mirror of
https://github.com/luau-lang/luau.git
synced 2024-12-13 13:30:40 +00:00
Sync to upstream/release/551 (#727)
* https://github.com/Roblox/luau/pull/719 * Improved `Failed to unify type packs` error message to be reported as `Type pack 'X' could not be converted into 'Y'` * https://github.com/Roblox/luau/pull/722 * 1% reduction in executed instruction count by removing a check in fast call dispatch * Additional fixes to reported error location of OOM errors in VM * Improve `math.sqrt`, `math.floor` and `math.ceil` performance on additional compilers and platforms (1-2% geomean improvement including 8-9% on math-cordic) * All thrown exceptions by Luau analysis are derived from `Luau::InternalCompilerError` * When a call site has fewer arguments than required, error now reports the location of the function name instead of the argument to the function * https://github.com/Roblox/luau/pull/724 * Fixed https://github.com/Roblox/luau/issues/725 Co-authored-by: Arseny Kapoulkine <arseny.kapoulkine@gmail.com> Co-authored-by: Andy Friesen <afriesen@roblox.com>
This commit is contained in:
parent
ad7d006fb2
commit
a6b5051edc
65 changed files with 3058 additions and 881 deletions
|
@ -23,6 +23,30 @@ using ScopePtr = std::shared_ptr<Scope>;
|
|||
|
||||
struct DcrLogger;
|
||||
|
||||
struct Inference
|
||||
{
|
||||
TypeId ty = nullptr;
|
||||
|
||||
Inference() = default;
|
||||
|
||||
explicit Inference(TypeId ty)
|
||||
: ty(ty)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
struct InferencePack
|
||||
{
|
||||
TypePackId tp = nullptr;
|
||||
|
||||
InferencePack() = default;
|
||||
|
||||
explicit InferencePack(TypePackId tp)
|
||||
: tp(tp)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
struct ConstraintGraphBuilder
|
||||
{
|
||||
// A list of all the scopes in the module. This vector holds ownership of the
|
||||
|
@ -130,8 +154,10 @@ struct ConstraintGraphBuilder
|
|||
void visit(const ScopePtr& scope, AstStatDeclareFunction* declareFunction);
|
||||
void visit(const ScopePtr& scope, AstStatError* error);
|
||||
|
||||
TypePackId checkPack(const ScopePtr& scope, AstArray<AstExpr*> exprs, const std::vector<TypeId>& expectedTypes = {});
|
||||
TypePackId checkPack(const ScopePtr& scope, AstExpr* expr, const std::vector<TypeId>& expectedTypes = {});
|
||||
InferencePack checkPack(const ScopePtr& scope, AstArray<AstExpr*> exprs, const std::vector<TypeId>& expectedTypes = {});
|
||||
InferencePack checkPack(const ScopePtr& scope, AstExpr* expr, const std::vector<TypeId>& expectedTypes = {});
|
||||
|
||||
InferencePack checkPack(const ScopePtr& scope, AstExprCall* call, const std::vector<TypeId>& expectedTypes);
|
||||
|
||||
/**
|
||||
* Checks an expression that is expected to evaluate to one type.
|
||||
|
@ -141,18 +167,19 @@ struct ConstraintGraphBuilder
|
|||
* surrounding context. Used to implement bidirectional type checking.
|
||||
* @return the type of the expression.
|
||||
*/
|
||||
TypeId check(const ScopePtr& scope, AstExpr* expr, std::optional<TypeId> expectedType = {});
|
||||
Inference check(const ScopePtr& scope, AstExpr* expr, std::optional<TypeId> expectedType = {});
|
||||
|
||||
TypeId check(const ScopePtr& scope, AstExprLocal* local);
|
||||
TypeId check(const ScopePtr& scope, AstExprGlobal* global);
|
||||
TypeId check(const ScopePtr& scope, AstExprIndexName* indexName);
|
||||
TypeId check(const ScopePtr& scope, AstExprIndexExpr* indexExpr);
|
||||
TypeId check(const ScopePtr& scope, AstExprUnary* unary);
|
||||
TypeId check_(const ScopePtr& scope, AstExprUnary* unary);
|
||||
TypeId check(const ScopePtr& scope, AstExprBinary* binary, std::optional<TypeId> expectedType);
|
||||
TypeId check(const ScopePtr& scope, AstExprIfElse* ifElse, std::optional<TypeId> expectedType);
|
||||
TypeId check(const ScopePtr& scope, AstExprTypeAssertion* typeAssert);
|
||||
TypeId check(const ScopePtr& scope, AstExprTable* expr, std::optional<TypeId> expectedType);
|
||||
Inference check(const ScopePtr& scope, AstExprConstantString* string, std::optional<TypeId> expectedType);
|
||||
Inference check(const ScopePtr& scope, AstExprConstantBool* bool_, std::optional<TypeId> expectedType);
|
||||
Inference check(const ScopePtr& scope, AstExprLocal* local);
|
||||
Inference check(const ScopePtr& scope, AstExprGlobal* global);
|
||||
Inference check(const ScopePtr& scope, AstExprIndexName* indexName);
|
||||
Inference check(const ScopePtr& scope, AstExprIndexExpr* indexExpr);
|
||||
Inference check(const ScopePtr& scope, AstExprUnary* unary);
|
||||
Inference check(const ScopePtr& scope, AstExprBinary* binary, std::optional<TypeId> expectedType);
|
||||
Inference check(const ScopePtr& scope, AstExprIfElse* ifElse, std::optional<TypeId> expectedType);
|
||||
Inference check(const ScopePtr& scope, AstExprTypeAssertion* typeAssert);
|
||||
Inference check(const ScopePtr& scope, AstExprTable* expr, std::optional<TypeId> expectedType);
|
||||
|
||||
TypePackId checkLValues(const ScopePtr& scope, AstArray<AstExpr*> exprs);
|
||||
|
||||
|
@ -202,7 +229,7 @@ struct ConstraintGraphBuilder
|
|||
std::vector<std::pair<Name, GenericTypeDefinition>> createGenerics(const ScopePtr& scope, AstArray<AstGenericType> generics);
|
||||
std::vector<std::pair<Name, GenericTypePackDefinition>> createGenericPacks(const ScopePtr& scope, AstArray<AstGenericTypePack> packs);
|
||||
|
||||
TypeId flattenPack(const ScopePtr& scope, Location location, TypePackId tp);
|
||||
Inference flattenPack(const ScopePtr& scope, Location location, InferencePack pack);
|
||||
|
||||
void reportError(Location location, TypeErrorData err);
|
||||
void reportCodeTooComplex(Location location);
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
#include "Luau/Variant.h"
|
||||
#include "Luau/TypeArena.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauIceExceptionInheritanceChange)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
struct TypeError;
|
||||
|
@ -302,12 +304,20 @@ struct NormalizationTooComplex
|
|||
}
|
||||
};
|
||||
|
||||
struct TypePackMismatch
|
||||
{
|
||||
TypePackId wantedTp;
|
||||
TypePackId givenTp;
|
||||
|
||||
bool operator==(const TypePackMismatch& rhs) const;
|
||||
};
|
||||
|
||||
using TypeErrorData = Variant<TypeMismatch, UnknownSymbol, UnknownProperty, NotATable, CannotExtendTable, OnlyTablesCanHaveMethods,
|
||||
DuplicateTypeDefinition, CountMismatch, FunctionDoesNotTakeSelf, FunctionRequiresSelf, OccursCheckFailed, UnknownRequire,
|
||||
IncorrectGenericParameterCount, SyntaxError, CodeTooComplex, UnificationTooComplex, UnknownPropButFoundLikeProp, GenericError, InternalError,
|
||||
CannotCallNonFunction, ExtraInformation, DeprecatedApiUsed, ModuleHasCyclicDependency, IllegalRequire, FunctionExitsWithoutReturning,
|
||||
DuplicateGenericParameter, CannotInferBinaryOperation, MissingProperties, SwappedGenericTypeParameter, OptionalValueAccess, MissingUnionProperty,
|
||||
TypesAreUnrelated, NormalizationTooComplex>;
|
||||
TypesAreUnrelated, NormalizationTooComplex, TypePackMismatch>;
|
||||
|
||||
struct TypeError
|
||||
{
|
||||
|
@ -374,6 +384,10 @@ struct InternalErrorReporter
|
|||
class InternalCompilerError : public std::exception
|
||||
{
|
||||
public:
|
||||
explicit InternalCompilerError(const std::string& message)
|
||||
: message(message)
|
||||
{
|
||||
}
|
||||
explicit InternalCompilerError(const std::string& message, const std::string& moduleName)
|
||||
: message(message)
|
||||
, moduleName(moduleName)
|
||||
|
@ -388,8 +402,14 @@ public:
|
|||
virtual const char* what() const throw();
|
||||
|
||||
const std::string message;
|
||||
const std::string moduleName;
|
||||
const std::optional<std::string> moduleName;
|
||||
const std::optional<Location> location;
|
||||
};
|
||||
|
||||
// These two function overloads only exist to facilitate fast flagging a change to InternalCompilerError
|
||||
// Both functions can be removed when FFlagLuauIceExceptionInheritanceChange is removed and calling code
|
||||
// can directly throw InternalCompilerError.
|
||||
[[noreturn]] void throwRuntimeError(const std::string& message);
|
||||
[[noreturn]] void throwRuntimeError(const std::string& message, const std::string& moduleName);
|
||||
|
||||
} // namespace Luau
|
||||
|
|
|
@ -106,9 +106,68 @@ struct std::equal_to<const Luau::TypeIds*>
|
|||
namespace Luau
|
||||
{
|
||||
|
||||
// A normalized string type is either `string` (represented by `nullopt`)
|
||||
// or a union of string singletons.
|
||||
using NormalizedStringType = std::optional<std::map<std::string, TypeId>>;
|
||||
/** A normalized string type is either `string` (represented by `nullopt`) or a
|
||||
* union of string singletons.
|
||||
*
|
||||
* When FFlagLuauNegatedStringSingletons is unset, the representation is as
|
||||
* follows:
|
||||
*
|
||||
* * The `string` data type is represented by the option `singletons` having the
|
||||
* value `std::nullopt`.
|
||||
* * The type `never` is represented by `singletons` being populated with an
|
||||
* empty map.
|
||||
* * A union of string singletons is represented by a map populated by the names
|
||||
* and TypeIds of the singletons contained therein.
|
||||
*
|
||||
* When FFlagLuauNegatedStringSingletons is set, the representation is as
|
||||
* follows:
|
||||
*
|
||||
* * A union of string singletons is finite and includes the singletons named by
|
||||
* the `singletons` field.
|
||||
* * An intersection of negated string singletons is cofinite and includes the
|
||||
* singletons excluded by the `singletons` field. It is implied that cofinite
|
||||
* values are exclusions from `string` itself.
|
||||
* * The `string` data type is a cofinite set minus zero elements.
|
||||
* * The `never` data type is a finite set plus zero elements.
|
||||
*/
|
||||
struct NormalizedStringType
|
||||
{
|
||||
// When false, this type represents a union of singleton string types.
|
||||
// eg "a" | "b" | "c"
|
||||
//
|
||||
// When true, this type represents string intersected with negated string
|
||||
// singleton types.
|
||||
// eg string & ~"a" & ~"b" & ...
|
||||
bool isCofinite = false;
|
||||
|
||||
// TODO: This field cannot be nullopt when FFlagLuauNegatedStringSingletons
|
||||
// is set. When clipping that flag, we can remove the wrapping optional.
|
||||
std::optional<std::map<std::string, TypeId>> singletons;
|
||||
|
||||
void resetToString();
|
||||
void resetToNever();
|
||||
|
||||
bool isNever() const;
|
||||
bool isString() const;
|
||||
|
||||
/// Returns true if the string has finite domain.
|
||||
///
|
||||
/// Important subtlety: This method returns true for `never`. The empty set
|
||||
/// is indeed an empty set.
|
||||
bool isUnion() const;
|
||||
|
||||
/// Returns true if the string has infinite domain.
|
||||
bool isIntersection() const;
|
||||
|
||||
bool includes(const std::string& str) const;
|
||||
|
||||
static const NormalizedStringType never;
|
||||
|
||||
NormalizedStringType() = default;
|
||||
NormalizedStringType(bool isCofinite, std::optional<std::map<std::string, TypeId>> singletons);
|
||||
};
|
||||
|
||||
bool isSubtype(const NormalizedStringType& subStr, const NormalizedStringType& superStr);
|
||||
|
||||
// A normalized function type is either `never` (represented by `nullopt`)
|
||||
// or an intersection of function types.
|
||||
|
@ -157,7 +216,7 @@ struct NormalizedType
|
|||
|
||||
// The string part of the type.
|
||||
// This may be the `string` type, or a union of singletons.
|
||||
NormalizedStringType strings = std::map<std::string, TypeId>{};
|
||||
NormalizedStringType strings;
|
||||
|
||||
// The thread part of the type.
|
||||
// This type is either never or thread.
|
||||
|
@ -231,8 +290,14 @@ public:
|
|||
bool unionNormals(NormalizedType& here, const NormalizedType& there, int ignoreSmallerTyvars = -1);
|
||||
bool unionNormalWithTy(NormalizedType& here, TypeId there, int ignoreSmallerTyvars = -1);
|
||||
|
||||
// ------- Negations
|
||||
NormalizedType negateNormal(const NormalizedType& here);
|
||||
TypeIds negateAll(const TypeIds& theres);
|
||||
TypeId negate(TypeId there);
|
||||
void subtractPrimitive(NormalizedType& here, TypeId ty);
|
||||
void subtractSingleton(NormalizedType& here, TypeId ty);
|
||||
|
||||
// ------- Normalizing intersections
|
||||
void intersectTysWithTy(TypeIds& here, TypeId there);
|
||||
TypeId intersectionOfTops(TypeId here, TypeId there);
|
||||
TypeId intersectionOfBools(TypeId here, TypeId there);
|
||||
void intersectClasses(TypeIds& heres, const TypeIds& theres);
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#pragma once
|
||||
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/Error.h"
|
||||
|
||||
#include <stdexcept>
|
||||
#include <exception>
|
||||
|
@ -9,10 +10,20 @@
|
|||
namespace Luau
|
||||
{
|
||||
|
||||
struct RecursionLimitException : public std::exception
|
||||
struct RecursionLimitException : public InternalCompilerError
|
||||
{
|
||||
RecursionLimitException()
|
||||
: InternalCompilerError("Internal recursion counter limit exceeded")
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauIceExceptionInheritanceChange);
|
||||
}
|
||||
};
|
||||
|
||||
struct RecursionLimitException_DEPRECATED : public std::exception
|
||||
{
|
||||
const char* what() const noexcept
|
||||
{
|
||||
LUAU_ASSERT(!FFlag::LuauIceExceptionInheritanceChange);
|
||||
return "Internal recursion counter limit exceeded";
|
||||
}
|
||||
};
|
||||
|
@ -42,7 +53,14 @@ struct RecursionLimiter : RecursionCounter
|
|||
{
|
||||
if (limit > 0 && *count > limit)
|
||||
{
|
||||
throw RecursionLimitException();
|
||||
if (FFlag::LuauIceExceptionInheritanceChange)
|
||||
{
|
||||
throw RecursionLimitException();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw RecursionLimitException_DEPRECATED();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -117,6 +117,8 @@ inline std::string toStringNamedFunction(const std::string& funcName, const Func
|
|||
return toStringNamedFunction(funcName, ftv, opts);
|
||||
}
|
||||
|
||||
std::optional<std::string> getFunctionNameAsString(const AstExpr& expr);
|
||||
|
||||
// It could be useful to see the text representation of a type during a debugging session instead of exploring the content of the class
|
||||
// These functions will dump the type to stdout and can be evaluated in Watch/Immediate windows or as gdb/lldb expression
|
||||
std::string dump(TypeId ty);
|
||||
|
|
|
@ -48,7 +48,17 @@ struct HashBoolNamePair
|
|||
size_t operator()(const std::pair<bool, Name>& pair) const;
|
||||
};
|
||||
|
||||
class TimeLimitError : public std::exception
|
||||
class TimeLimitError : public InternalCompilerError
|
||||
{
|
||||
public:
|
||||
explicit TimeLimitError(const std::string& moduleName)
|
||||
: InternalCompilerError("Typeinfer failed to complete in allotted time", moduleName)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauIceExceptionInheritanceChange);
|
||||
}
|
||||
};
|
||||
|
||||
class TimeLimitError_DEPRECATED : public std::exception
|
||||
{
|
||||
public:
|
||||
virtual const char* what() const throw();
|
||||
|
@ -236,6 +246,7 @@ public:
|
|||
|
||||
[[noreturn]] void ice(const std::string& message, const Location& location);
|
||||
[[noreturn]] void ice(const std::string& message);
|
||||
[[noreturn]] void throwTimeLimitError();
|
||||
|
||||
ScopePtr childFunctionScope(const ScopePtr& parent, const Location& location, int subLevel = 0);
|
||||
ScopePtr childScope(const ScopePtr& parent, const Location& location);
|
||||
|
|
|
@ -96,6 +96,8 @@ private:
|
|||
void tryUnifyScalarShape(TypeId subTy, TypeId superTy, bool reversed);
|
||||
void tryUnifyWithMetatable(TypeId subTy, TypeId superTy, bool reversed);
|
||||
void tryUnifyWithClass(TypeId subTy, TypeId superTy, bool reversed);
|
||||
void tryUnifyTypeWithNegation(TypeId subTy, TypeId superTy);
|
||||
void tryUnifyNegationWithType(TypeId subTy, TypeId superTy);
|
||||
|
||||
TypePackId tryApplyOverloadedFunction(TypeId function, const NormalizedFunctionType& overloads, TypePackId args);
|
||||
|
||||
|
|
|
@ -11,6 +11,8 @@
|
|||
|
||||
#include <algorithm>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauCheckOverloadedDocSymbol, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
|
@ -430,6 +432,8 @@ ExprOrLocal findExprOrLocalAtPosition(const SourceModule& source, Position pos)
|
|||
static std::optional<DocumentationSymbol> checkOverloadedDocumentationSymbol(
|
||||
const Module& module, const TypeId ty, const AstExpr* parentExpr, const std::optional<DocumentationSymbol> documentationSymbol)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauCheckOverloadedDocSymbol);
|
||||
|
||||
if (!documentationSymbol)
|
||||
return std::nullopt;
|
||||
|
||||
|
@ -466,7 +470,38 @@ std::optional<DocumentationSymbol> getDocumentationSymbolAtPosition(const Source
|
|||
|
||||
if (std::optional<Binding> binding = findBindingAtPosition(module, source, position))
|
||||
{
|
||||
return checkOverloadedDocumentationSymbol(module, binding->typeId, parentExpr, binding->documentationSymbol);
|
||||
if (FFlag::LuauCheckOverloadedDocSymbol)
|
||||
{
|
||||
return checkOverloadedDocumentationSymbol(module, binding->typeId, parentExpr, binding->documentationSymbol);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (binding->documentationSymbol)
|
||||
{
|
||||
// This might be an overloaded function binding.
|
||||
if (get<IntersectionTypeVar>(follow(binding->typeId)))
|
||||
{
|
||||
TypeId matchingOverload = nullptr;
|
||||
if (parentExpr && parentExpr->is<AstExprCall>())
|
||||
{
|
||||
if (auto it = module.astOverloadResolvedTypes.find(parentExpr))
|
||||
{
|
||||
matchingOverload = *it;
|
||||
}
|
||||
}
|
||||
|
||||
if (matchingOverload)
|
||||
{
|
||||
std::string overloadSymbol = *binding->documentationSymbol + "/overload/";
|
||||
// Default toString options are fine for this purpose.
|
||||
overloadSymbol += toString(matchingOverload);
|
||||
return overloadSymbol;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return binding->documentationSymbol;
|
||||
}
|
||||
}
|
||||
|
||||
if (targetExpr)
|
||||
|
@ -480,14 +515,20 @@ std::optional<DocumentationSymbol> getDocumentationSymbolAtPosition(const Source
|
|||
{
|
||||
if (auto propIt = ttv->props.find(indexName->index.value); propIt != ttv->props.end())
|
||||
{
|
||||
return checkOverloadedDocumentationSymbol(module, propIt->second.type, parentExpr, propIt->second.documentationSymbol);
|
||||
if (FFlag::LuauCheckOverloadedDocSymbol)
|
||||
return checkOverloadedDocumentationSymbol(module, propIt->second.type, parentExpr, propIt->second.documentationSymbol);
|
||||
else
|
||||
return propIt->second.documentationSymbol;
|
||||
}
|
||||
}
|
||||
else if (const ClassTypeVar* ctv = get<ClassTypeVar>(parentTy))
|
||||
{
|
||||
if (auto propIt = ctv->props.find(indexName->index.value); propIt != ctv->props.end())
|
||||
{
|
||||
return checkOverloadedDocumentationSymbol(module, propIt->second.type, parentExpr, propIt->second.documentationSymbol);
|
||||
if (FFlag::LuauCheckOverloadedDocSymbol)
|
||||
return checkOverloadedDocumentationSymbol(module, propIt->second.type, parentExpr, propIt->second.documentationSymbol);
|
||||
else
|
||||
return propIt->second.documentationSymbol;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -263,7 +263,7 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocal* local)
|
|||
if (hasAnnotation)
|
||||
expectedTypes.insert(begin(expectedTypes), begin(varTypes) + i, end(varTypes));
|
||||
|
||||
TypePackId exprPack = checkPack(scope, value, expectedTypes);
|
||||
TypePackId exprPack = checkPack(scope, value, expectedTypes).tp;
|
||||
|
||||
if (i < local->vars.size)
|
||||
{
|
||||
|
@ -292,7 +292,7 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocal* local)
|
|||
if (hasAnnotation)
|
||||
expectedType = varTypes.at(i);
|
||||
|
||||
TypeId exprType = check(scope, value, expectedType);
|
||||
TypeId exprType = check(scope, value, expectedType).ty;
|
||||
if (i < varTypes.size())
|
||||
{
|
||||
if (varTypes[i])
|
||||
|
@ -350,7 +350,7 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFor* for_)
|
|||
if (!expr)
|
||||
return;
|
||||
|
||||
TypeId t = check(scope, expr);
|
||||
TypeId t = check(scope, expr).ty;
|
||||
addConstraint(scope, expr->location, SubtypeConstraint{t, singletonTypes->numberType});
|
||||
};
|
||||
|
||||
|
@ -368,7 +368,7 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatForIn* forIn)
|
|||
{
|
||||
ScopePtr loopScope = childScope(forIn, scope);
|
||||
|
||||
TypePackId iterator = checkPack(scope, forIn->values);
|
||||
TypePackId iterator = checkPack(scope, forIn->values).tp;
|
||||
|
||||
std::vector<TypeId> variableTypes;
|
||||
variableTypes.reserve(forIn->vars.size);
|
||||
|
@ -489,7 +489,7 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFunction* funct
|
|||
}
|
||||
else if (AstExprIndexName* indexName = function->name->as<AstExprIndexName>())
|
||||
{
|
||||
TypeId containingTableType = check(scope, indexName->expr);
|
||||
TypeId containingTableType = check(scope, indexName->expr).ty;
|
||||
|
||||
functionType = arena->addType(BlockedTypeVar{});
|
||||
|
||||
|
@ -531,7 +531,7 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatReturn* ret)
|
|||
for (TypeId ty : scope->returnType)
|
||||
expectedTypes.push_back(ty);
|
||||
|
||||
TypePackId exprTypes = checkPack(scope, ret->list, expectedTypes);
|
||||
TypePackId exprTypes = checkPack(scope, ret->list, expectedTypes).tp;
|
||||
addConstraint(scope, ret->location, PackSubtypeConstraint{exprTypes, scope->returnType});
|
||||
}
|
||||
|
||||
|
@ -545,7 +545,7 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatBlock* block)
|
|||
void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatAssign* assign)
|
||||
{
|
||||
TypePackId varPackId = checkLValues(scope, assign->vars);
|
||||
TypePackId valuePack = checkPack(scope, assign->values);
|
||||
TypePackId valuePack = checkPack(scope, assign->values).tp;
|
||||
|
||||
addConstraint(scope, assign->location, PackSubtypeConstraint{valuePack, varPackId});
|
||||
}
|
||||
|
@ -732,7 +732,6 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatDeclareClass* d
|
|||
|
||||
void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatDeclareFunction* global)
|
||||
{
|
||||
|
||||
std::vector<std::pair<Name, GenericTypeDefinition>> generics = createGenerics(scope, global->generics);
|
||||
std::vector<std::pair<Name, GenericTypePackDefinition>> genericPacks = createGenericPacks(scope, global->genericPacks);
|
||||
|
||||
|
@ -779,7 +778,7 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatError* error)
|
|||
check(scope, expr);
|
||||
}
|
||||
|
||||
TypePackId ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstArray<AstExpr*> exprs, const std::vector<TypeId>& expectedTypes)
|
||||
InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstArray<AstExpr*> exprs, const std::vector<TypeId>& expectedTypes)
|
||||
{
|
||||
std::vector<TypeId> head;
|
||||
std::optional<TypePackId> tail;
|
||||
|
@ -792,201 +791,180 @@ TypePackId ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstArray<Ast
|
|||
std::optional<TypeId> expectedType;
|
||||
if (i < expectedTypes.size())
|
||||
expectedType = expectedTypes[i];
|
||||
head.push_back(check(scope, expr));
|
||||
head.push_back(check(scope, expr).ty);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::vector<TypeId> expectedTailTypes;
|
||||
if (i < expectedTypes.size())
|
||||
expectedTailTypes.assign(begin(expectedTypes) + i, end(expectedTypes));
|
||||
tail = checkPack(scope, expr, expectedTailTypes);
|
||||
tail = checkPack(scope, expr, expectedTailTypes).tp;
|
||||
}
|
||||
}
|
||||
|
||||
if (head.empty() && tail)
|
||||
return *tail;
|
||||
return InferencePack{*tail};
|
||||
else
|
||||
return arena->addTypePack(TypePack{std::move(head), tail});
|
||||
return InferencePack{arena->addTypePack(TypePack{std::move(head), tail})};
|
||||
}
|
||||
|
||||
TypePackId ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExpr* expr, const std::vector<TypeId>& expectedTypes)
|
||||
InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExpr* expr, const std::vector<TypeId>& expectedTypes)
|
||||
{
|
||||
RecursionCounter counter{&recursionCount};
|
||||
|
||||
if (recursionCount >= FInt::LuauCheckRecursionLimit)
|
||||
{
|
||||
reportCodeTooComplex(expr->location);
|
||||
return singletonTypes->errorRecoveryTypePack();
|
||||
return InferencePack{singletonTypes->errorRecoveryTypePack()};
|
||||
}
|
||||
|
||||
TypePackId result = nullptr;
|
||||
InferencePack result;
|
||||
|
||||
if (AstExprCall* call = expr->as<AstExprCall>())
|
||||
{
|
||||
TypeId fnType = check(scope, call->func);
|
||||
const size_t constraintIndex = scope->constraints.size();
|
||||
const size_t scopeIndex = scopes.size();
|
||||
|
||||
std::vector<TypeId> args;
|
||||
|
||||
for (AstExpr* arg : call->args)
|
||||
{
|
||||
args.push_back(check(scope, arg));
|
||||
}
|
||||
|
||||
// TODO self
|
||||
|
||||
if (matchSetmetatable(*call))
|
||||
{
|
||||
LUAU_ASSERT(args.size() == 2);
|
||||
TypeId target = args[0];
|
||||
TypeId mt = args[1];
|
||||
|
||||
MetatableTypeVar mtv{target, mt};
|
||||
TypeId resultTy = arena->addType(mtv);
|
||||
result = arena->addTypePack({resultTy});
|
||||
}
|
||||
else
|
||||
{
|
||||
const size_t constraintEndIndex = scope->constraints.size();
|
||||
const size_t scopeEndIndex = scopes.size();
|
||||
|
||||
astOriginalCallTypes[call->func] = fnType;
|
||||
|
||||
TypeId instantiatedType = arena->addType(BlockedTypeVar{});
|
||||
// TODO: How do expectedTypes play into this? Do they?
|
||||
TypePackId rets = arena->addTypePack(BlockedTypePack{});
|
||||
TypePackId argPack = arena->addTypePack(TypePack{args, {}});
|
||||
FunctionTypeVar ftv(TypeLevel{}, scope.get(), argPack, rets);
|
||||
TypeId inferredFnType = arena->addType(ftv);
|
||||
|
||||
scope->unqueuedConstraints.push_back(
|
||||
std::make_unique<Constraint>(NotNull{scope.get()}, call->func->location, InstantiationConstraint{instantiatedType, fnType}));
|
||||
NotNull<const Constraint> ic(scope->unqueuedConstraints.back().get());
|
||||
|
||||
scope->unqueuedConstraints.push_back(
|
||||
std::make_unique<Constraint>(NotNull{scope.get()}, call->func->location, SubtypeConstraint{inferredFnType, instantiatedType}));
|
||||
NotNull<Constraint> sc(scope->unqueuedConstraints.back().get());
|
||||
|
||||
// We force constraints produced by checking function arguments to wait
|
||||
// until after we have resolved the constraint on the function itself.
|
||||
// This ensures, for instance, that we start inferring the contents of
|
||||
// lambdas under the assumption that their arguments and return types
|
||||
// will be compatible with the enclosing function call.
|
||||
for (size_t ci = constraintIndex; ci < constraintEndIndex; ++ci)
|
||||
scope->constraints[ci]->dependencies.push_back(sc);
|
||||
|
||||
for (size_t si = scopeIndex; si < scopeEndIndex; ++si)
|
||||
{
|
||||
for (auto& c : scopes[si].second->constraints)
|
||||
{
|
||||
c->dependencies.push_back(sc);
|
||||
}
|
||||
}
|
||||
|
||||
addConstraint(scope, call->func->location,
|
||||
FunctionCallConstraint{
|
||||
{ic, sc},
|
||||
fnType,
|
||||
argPack,
|
||||
rets,
|
||||
call,
|
||||
});
|
||||
|
||||
result = rets;
|
||||
}
|
||||
}
|
||||
result = {checkPack(scope, call, expectedTypes)};
|
||||
else if (AstExprVarargs* varargs = expr->as<AstExprVarargs>())
|
||||
{
|
||||
if (scope->varargPack)
|
||||
result = *scope->varargPack;
|
||||
result = InferencePack{*scope->varargPack};
|
||||
else
|
||||
result = singletonTypes->errorRecoveryTypePack();
|
||||
result = InferencePack{singletonTypes->errorRecoveryTypePack()};
|
||||
}
|
||||
else
|
||||
{
|
||||
std::optional<TypeId> expectedType;
|
||||
if (!expectedTypes.empty())
|
||||
expectedType = expectedTypes[0];
|
||||
TypeId t = check(scope, expr, expectedType);
|
||||
result = arena->addTypePack({t});
|
||||
TypeId t = check(scope, expr, expectedType).ty;
|
||||
result = InferencePack{arena->addTypePack({t})};
|
||||
}
|
||||
|
||||
LUAU_ASSERT(result);
|
||||
astTypePacks[expr] = result;
|
||||
LUAU_ASSERT(result.tp);
|
||||
astTypePacks[expr] = result.tp;
|
||||
return result;
|
||||
}
|
||||
|
||||
TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExpr* expr, std::optional<TypeId> expectedType)
|
||||
InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExprCall* call, const std::vector<TypeId>& expectedTypes)
|
||||
{
|
||||
TypeId fnType = check(scope, call->func).ty;
|
||||
const size_t constraintIndex = scope->constraints.size();
|
||||
const size_t scopeIndex = scopes.size();
|
||||
|
||||
std::vector<TypeId> args;
|
||||
|
||||
for (AstExpr* arg : call->args)
|
||||
{
|
||||
args.push_back(check(scope, arg).ty);
|
||||
}
|
||||
|
||||
// TODO self
|
||||
|
||||
if (matchSetmetatable(*call))
|
||||
{
|
||||
LUAU_ASSERT(args.size() == 2);
|
||||
TypeId target = args[0];
|
||||
TypeId mt = args[1];
|
||||
|
||||
AstExpr* targetExpr = call->args.data[0];
|
||||
|
||||
MetatableTypeVar mtv{target, mt};
|
||||
TypeId resultTy = arena->addType(mtv);
|
||||
|
||||
if (AstExprLocal* targetLocal = targetExpr->as<AstExprLocal>())
|
||||
scope->bindings[targetLocal->local].typeId = resultTy;
|
||||
|
||||
return InferencePack{arena->addTypePack({resultTy})};
|
||||
}
|
||||
else
|
||||
{
|
||||
const size_t constraintEndIndex = scope->constraints.size();
|
||||
const size_t scopeEndIndex = scopes.size();
|
||||
|
||||
astOriginalCallTypes[call->func] = fnType;
|
||||
|
||||
TypeId instantiatedType = arena->addType(BlockedTypeVar{});
|
||||
// TODO: How do expectedTypes play into this? Do they?
|
||||
TypePackId rets = arena->addTypePack(BlockedTypePack{});
|
||||
TypePackId argPack = arena->addTypePack(TypePack{args, {}});
|
||||
FunctionTypeVar ftv(TypeLevel{}, scope.get(), argPack, rets);
|
||||
TypeId inferredFnType = arena->addType(ftv);
|
||||
|
||||
scope->unqueuedConstraints.push_back(
|
||||
std::make_unique<Constraint>(NotNull{scope.get()}, call->func->location, InstantiationConstraint{instantiatedType, fnType}));
|
||||
NotNull<const Constraint> ic(scope->unqueuedConstraints.back().get());
|
||||
|
||||
scope->unqueuedConstraints.push_back(
|
||||
std::make_unique<Constraint>(NotNull{scope.get()}, call->func->location, SubtypeConstraint{inferredFnType, instantiatedType}));
|
||||
NotNull<Constraint> sc(scope->unqueuedConstraints.back().get());
|
||||
|
||||
// We force constraints produced by checking function arguments to wait
|
||||
// until after we have resolved the constraint on the function itself.
|
||||
// This ensures, for instance, that we start inferring the contents of
|
||||
// lambdas under the assumption that their arguments and return types
|
||||
// will be compatible with the enclosing function call.
|
||||
for (size_t ci = constraintIndex; ci < constraintEndIndex; ++ci)
|
||||
scope->constraints[ci]->dependencies.push_back(sc);
|
||||
|
||||
for (size_t si = scopeIndex; si < scopeEndIndex; ++si)
|
||||
{
|
||||
for (auto& c : scopes[si].second->constraints)
|
||||
{
|
||||
c->dependencies.push_back(sc);
|
||||
}
|
||||
}
|
||||
|
||||
addConstraint(scope, call->func->location,
|
||||
FunctionCallConstraint{
|
||||
{ic, sc},
|
||||
fnType,
|
||||
argPack,
|
||||
rets,
|
||||
call,
|
||||
});
|
||||
|
||||
return InferencePack{rets};
|
||||
}
|
||||
}
|
||||
|
||||
Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExpr* expr, std::optional<TypeId> expectedType)
|
||||
{
|
||||
RecursionCounter counter{&recursionCount};
|
||||
|
||||
if (recursionCount >= FInt::LuauCheckRecursionLimit)
|
||||
{
|
||||
reportCodeTooComplex(expr->location);
|
||||
return singletonTypes->errorRecoveryType();
|
||||
return Inference{singletonTypes->errorRecoveryType()};
|
||||
}
|
||||
|
||||
TypeId result = nullptr;
|
||||
Inference result;
|
||||
|
||||
if (auto group = expr->as<AstExprGroup>())
|
||||
result = check(scope, group->expr, expectedType);
|
||||
else if (auto stringExpr = expr->as<AstExprConstantString>())
|
||||
{
|
||||
if (expectedType)
|
||||
{
|
||||
const TypeId expectedTy = follow(*expectedType);
|
||||
if (get<BlockedTypeVar>(expectedTy) || get<PendingExpansionTypeVar>(expectedTy))
|
||||
{
|
||||
result = arena->addType(BlockedTypeVar{});
|
||||
TypeId singletonType = arena->addType(SingletonTypeVar(StringSingleton{std::string(stringExpr->value.data, stringExpr->value.size)}));
|
||||
addConstraint(scope, expr->location, PrimitiveTypeConstraint{result, expectedTy, singletonType, singletonTypes->stringType});
|
||||
}
|
||||
else if (maybeSingleton(expectedTy))
|
||||
result = arena->addType(SingletonTypeVar{StringSingleton{std::string{stringExpr->value.data, stringExpr->value.size}}});
|
||||
else
|
||||
result = singletonTypes->stringType;
|
||||
}
|
||||
else
|
||||
result = singletonTypes->stringType;
|
||||
}
|
||||
result = check(scope, stringExpr, expectedType);
|
||||
else if (expr->is<AstExprConstantNumber>())
|
||||
result = singletonTypes->numberType;
|
||||
result = Inference{singletonTypes->numberType};
|
||||
else if (auto boolExpr = expr->as<AstExprConstantBool>())
|
||||
{
|
||||
if (expectedType)
|
||||
{
|
||||
const TypeId expectedTy = follow(*expectedType);
|
||||
const TypeId singletonType = boolExpr->value ? singletonTypes->trueType : singletonTypes->falseType;
|
||||
|
||||
if (get<BlockedTypeVar>(expectedTy) || get<PendingExpansionTypeVar>(expectedTy))
|
||||
{
|
||||
result = arena->addType(BlockedTypeVar{});
|
||||
addConstraint(scope, expr->location, PrimitiveTypeConstraint{result, expectedTy, singletonType, singletonTypes->booleanType});
|
||||
}
|
||||
else if (maybeSingleton(expectedTy))
|
||||
result = singletonType;
|
||||
else
|
||||
result = singletonTypes->booleanType;
|
||||
}
|
||||
else
|
||||
result = singletonTypes->booleanType;
|
||||
}
|
||||
result = check(scope, boolExpr, expectedType);
|
||||
else if (expr->is<AstExprConstantNil>())
|
||||
result = singletonTypes->nilType;
|
||||
result = Inference{singletonTypes->nilType};
|
||||
else if (auto local = expr->as<AstExprLocal>())
|
||||
result = check(scope, local);
|
||||
else if (auto global = expr->as<AstExprGlobal>())
|
||||
result = check(scope, global);
|
||||
else if (expr->is<AstExprVarargs>())
|
||||
result = flattenPack(scope, expr->location, checkPack(scope, expr));
|
||||
else if (expr->is<AstExprCall>())
|
||||
result = flattenPack(scope, expr->location, checkPack(scope, expr)); // TODO: needs predicates too
|
||||
else if (auto call = expr->as<AstExprCall>())
|
||||
{
|
||||
std::vector<TypeId> expectedTypes;
|
||||
if (expectedType)
|
||||
expectedTypes.push_back(*expectedType);
|
||||
result = flattenPack(scope, expr->location, checkPack(scope, call, expectedTypes)); // TODO: needs predicates too
|
||||
}
|
||||
else if (auto a = expr->as<AstExprFunction>())
|
||||
{
|
||||
FunctionSignature sig = checkFunctionSignature(scope, a);
|
||||
checkFunctionBody(sig.bodyScope, a);
|
||||
return sig.signature;
|
||||
return Inference{sig.signature};
|
||||
}
|
||||
else if (auto indexName = expr->as<AstExprIndexName>())
|
||||
result = check(scope, indexName);
|
||||
|
@ -1008,20 +986,63 @@ TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExpr* expr, std::
|
|||
for (AstExpr* subExpr : err->expressions)
|
||||
check(scope, subExpr);
|
||||
|
||||
result = singletonTypes->errorRecoveryType();
|
||||
result = Inference{singletonTypes->errorRecoveryType()};
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_ASSERT(0);
|
||||
result = freshType(scope);
|
||||
result = Inference{freshType(scope)};
|
||||
}
|
||||
|
||||
LUAU_ASSERT(result);
|
||||
astTypes[expr] = result;
|
||||
LUAU_ASSERT(result.ty);
|
||||
astTypes[expr] = result.ty;
|
||||
return result;
|
||||
}
|
||||
|
||||
TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprLocal* local)
|
||||
Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprConstantString* string, std::optional<TypeId> expectedType)
|
||||
{
|
||||
if (expectedType)
|
||||
{
|
||||
const TypeId expectedTy = follow(*expectedType);
|
||||
if (get<BlockedTypeVar>(expectedTy) || get<PendingExpansionTypeVar>(expectedTy))
|
||||
{
|
||||
TypeId ty = arena->addType(BlockedTypeVar{});
|
||||
TypeId singletonType = arena->addType(SingletonTypeVar(StringSingleton{std::string(string->value.data, string->value.size)}));
|
||||
addConstraint(scope, string->location, PrimitiveTypeConstraint{ty, expectedTy, singletonType, singletonTypes->stringType});
|
||||
return Inference{ty};
|
||||
}
|
||||
else if (maybeSingleton(expectedTy))
|
||||
return Inference{arena->addType(SingletonTypeVar{StringSingleton{std::string{string->value.data, string->value.size}}})};
|
||||
|
||||
return Inference{singletonTypes->stringType};
|
||||
}
|
||||
|
||||
return Inference{singletonTypes->stringType};
|
||||
}
|
||||
|
||||
Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprConstantBool* boolExpr, std::optional<TypeId> expectedType)
|
||||
{
|
||||
if (expectedType)
|
||||
{
|
||||
const TypeId expectedTy = follow(*expectedType);
|
||||
const TypeId singletonType = boolExpr->value ? singletonTypes->trueType : singletonTypes->falseType;
|
||||
|
||||
if (get<BlockedTypeVar>(expectedTy) || get<PendingExpansionTypeVar>(expectedTy))
|
||||
{
|
||||
TypeId ty = arena->addType(BlockedTypeVar{});
|
||||
addConstraint(scope, boolExpr->location, PrimitiveTypeConstraint{ty, expectedTy, singletonType, singletonTypes->booleanType});
|
||||
return Inference{ty};
|
||||
}
|
||||
else if (maybeSingleton(expectedTy))
|
||||
return Inference{singletonType};
|
||||
|
||||
return Inference{singletonTypes->booleanType};
|
||||
}
|
||||
|
||||
return Inference{singletonTypes->booleanType};
|
||||
}
|
||||
|
||||
Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprLocal* local)
|
||||
{
|
||||
std::optional<TypeId> resultTy;
|
||||
|
||||
|
@ -1035,26 +1056,26 @@ TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprLocal* local)
|
|||
}
|
||||
|
||||
if (!resultTy)
|
||||
return singletonTypes->errorRecoveryType(); // TODO: replace with ice, locals should never exist before its definition.
|
||||
return Inference{singletonTypes->errorRecoveryType()}; // TODO: replace with ice, locals should never exist before its definition.
|
||||
|
||||
return *resultTy;
|
||||
return Inference{*resultTy};
|
||||
}
|
||||
|
||||
TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprGlobal* global)
|
||||
Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprGlobal* global)
|
||||
{
|
||||
if (std::optional<TypeId> ty = scope->lookup(global->name))
|
||||
return *ty;
|
||||
return Inference{*ty};
|
||||
|
||||
/* prepopulateGlobalScope() has already added all global functions to the environment by this point, so any
|
||||
* global that is not already in-scope is definitely an unknown symbol.
|
||||
*/
|
||||
reportError(global->location, UnknownSymbol{global->name.value});
|
||||
return singletonTypes->errorRecoveryType();
|
||||
return Inference{singletonTypes->errorRecoveryType()};
|
||||
}
|
||||
|
||||
TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprIndexName* indexName)
|
||||
Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprIndexName* indexName)
|
||||
{
|
||||
TypeId obj = check(scope, indexName->expr);
|
||||
TypeId obj = check(scope, indexName->expr).ty;
|
||||
TypeId result = freshType(scope);
|
||||
|
||||
TableTypeVar::Props props{{indexName->index.value, Property{result}}};
|
||||
|
@ -1065,13 +1086,13 @@ TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprIndexName* in
|
|||
|
||||
addConstraint(scope, indexName->expr->location, SubtypeConstraint{obj, expectedTableType});
|
||||
|
||||
return result;
|
||||
return Inference{result};
|
||||
}
|
||||
|
||||
TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprIndexExpr* indexExpr)
|
||||
Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprIndexExpr* indexExpr)
|
||||
{
|
||||
TypeId obj = check(scope, indexExpr->expr);
|
||||
TypeId indexType = check(scope, indexExpr->index);
|
||||
TypeId obj = check(scope, indexExpr->expr).ty;
|
||||
TypeId indexType = check(scope, indexExpr->index).ty;
|
||||
|
||||
TypeId result = freshType(scope);
|
||||
|
||||
|
@ -1081,61 +1102,49 @@ TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprIndexExpr* in
|
|||
|
||||
addConstraint(scope, indexExpr->expr->location, SubtypeConstraint{obj, tableType});
|
||||
|
||||
return result;
|
||||
return Inference{result};
|
||||
}
|
||||
|
||||
TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprUnary* unary)
|
||||
Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprUnary* unary)
|
||||
{
|
||||
TypeId operandType = check_(scope, unary);
|
||||
TypeId operandType = check(scope, unary->expr).ty;
|
||||
TypeId resultType = arena->addType(BlockedTypeVar{});
|
||||
addConstraint(scope, unary->location, UnaryConstraint{unary->op, operandType, resultType});
|
||||
return resultType;
|
||||
return Inference{resultType};
|
||||
}
|
||||
|
||||
TypeId ConstraintGraphBuilder::check_(const ScopePtr& scope, AstExprUnary* unary)
|
||||
Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprBinary* binary, std::optional<TypeId> expectedType)
|
||||
{
|
||||
if (unary->op == AstExprUnary::Not)
|
||||
{
|
||||
TypeId ty = check(scope, unary->expr, std::nullopt);
|
||||
|
||||
return ty;
|
||||
}
|
||||
|
||||
return check(scope, unary->expr);
|
||||
}
|
||||
|
||||
TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprBinary* binary, std::optional<TypeId> expectedType)
|
||||
{
|
||||
TypeId leftType = check(scope, binary->left, expectedType);
|
||||
TypeId rightType = check(scope, binary->right, expectedType);
|
||||
TypeId leftType = check(scope, binary->left, expectedType).ty;
|
||||
TypeId rightType = check(scope, binary->right, expectedType).ty;
|
||||
|
||||
TypeId resultType = arena->addType(BlockedTypeVar{});
|
||||
addConstraint(scope, binary->location, BinaryConstraint{binary->op, leftType, rightType, resultType});
|
||||
return resultType;
|
||||
return Inference{resultType};
|
||||
}
|
||||
|
||||
TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprIfElse* ifElse, std::optional<TypeId> expectedType)
|
||||
Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprIfElse* ifElse, std::optional<TypeId> expectedType)
|
||||
{
|
||||
check(scope, ifElse->condition);
|
||||
|
||||
TypeId thenType = check(scope, ifElse->trueExpr, expectedType);
|
||||
TypeId elseType = check(scope, ifElse->falseExpr, expectedType);
|
||||
TypeId thenType = check(scope, ifElse->trueExpr, expectedType).ty;
|
||||
TypeId elseType = check(scope, ifElse->falseExpr, expectedType).ty;
|
||||
|
||||
if (ifElse->hasElse)
|
||||
{
|
||||
TypeId resultType = expectedType ? *expectedType : freshType(scope);
|
||||
addConstraint(scope, ifElse->trueExpr->location, SubtypeConstraint{thenType, resultType});
|
||||
addConstraint(scope, ifElse->falseExpr->location, SubtypeConstraint{elseType, resultType});
|
||||
return resultType;
|
||||
return Inference{resultType};
|
||||
}
|
||||
|
||||
return thenType;
|
||||
return Inference{thenType};
|
||||
}
|
||||
|
||||
TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprTypeAssertion* typeAssert)
|
||||
Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprTypeAssertion* typeAssert)
|
||||
{
|
||||
check(scope, typeAssert->expr, std::nullopt);
|
||||
return resolveType(scope, typeAssert->annotation);
|
||||
return Inference{resolveType(scope, typeAssert->annotation)};
|
||||
}
|
||||
|
||||
TypePackId ConstraintGraphBuilder::checkLValues(const ScopePtr& scope, AstArray<AstExpr*> exprs)
|
||||
|
@ -1286,22 +1295,22 @@ TypeId ConstraintGraphBuilder::checkLValue(const ScopePtr& scope, AstExpr* expr)
|
|||
|
||||
auto dottedPath = extractDottedName(expr);
|
||||
if (!dottedPath)
|
||||
return check(scope, expr);
|
||||
return check(scope, expr).ty;
|
||||
const auto [sym, segments] = std::move(*dottedPath);
|
||||
|
||||
if (!sym.local)
|
||||
return check(scope, expr);
|
||||
return check(scope, expr).ty;
|
||||
|
||||
auto lookupResult = scope->lookupEx(sym);
|
||||
if (!lookupResult)
|
||||
return check(scope, expr);
|
||||
return check(scope, expr).ty;
|
||||
const auto [ty, symbolScope] = std::move(*lookupResult);
|
||||
|
||||
TypeId replaceTy = arena->freshType(scope.get());
|
||||
|
||||
std::optional<TypeId> updatedType = updateTheTableType(arena, ty, segments, replaceTy);
|
||||
if (!updatedType)
|
||||
return check(scope, expr);
|
||||
return check(scope, expr).ty;
|
||||
|
||||
std::optional<DefId> def = dfg->getDef(sym);
|
||||
LUAU_ASSERT(def);
|
||||
|
@ -1310,7 +1319,7 @@ TypeId ConstraintGraphBuilder::checkLValue(const ScopePtr& scope, AstExpr* expr)
|
|||
return replaceTy;
|
||||
}
|
||||
|
||||
TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprTable* expr, std::optional<TypeId> expectedType)
|
||||
Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprTable* expr, std::optional<TypeId> expectedType)
|
||||
{
|
||||
TypeId ty = arena->addType(TableTypeVar{});
|
||||
TableTypeVar* ttv = getMutable<TableTypeVar>(ty);
|
||||
|
@ -1344,16 +1353,14 @@ TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprTable* expr,
|
|||
}
|
||||
}
|
||||
|
||||
TypeId itemTy = check(scope, item.value, expectedValueType);
|
||||
if (get<ErrorTypeVar>(follow(itemTy)))
|
||||
return ty;
|
||||
TypeId itemTy = check(scope, item.value, expectedValueType).ty;
|
||||
|
||||
if (item.key)
|
||||
{
|
||||
// Even though we don't need to use the type of the item's key if
|
||||
// it's a string constant, we still want to check it to populate
|
||||
// astTypes.
|
||||
TypeId keyTy = check(scope, item.key);
|
||||
TypeId keyTy = check(scope, item.key).ty;
|
||||
|
||||
if (AstExprConstantString* key = item.key->as<AstExprConstantString>())
|
||||
{
|
||||
|
@ -1373,7 +1380,7 @@ TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprTable* expr,
|
|||
}
|
||||
}
|
||||
|
||||
return ty;
|
||||
return Inference{ty};
|
||||
}
|
||||
|
||||
ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionSignature(const ScopePtr& parent, AstExprFunction* fn)
|
||||
|
@ -1541,9 +1548,18 @@ TypeId ConstraintGraphBuilder::resolveType(const ScopePtr& scope, AstType* ty, b
|
|||
}
|
||||
}
|
||||
|
||||
std::optional<TypeFun> alias = scope->lookupType(ref->name.value);
|
||||
std::optional<TypeFun> alias;
|
||||
|
||||
if (alias.has_value() || ref->prefix.has_value())
|
||||
if (ref->prefix.has_value())
|
||||
{
|
||||
alias = scope->lookupImportedType(ref->prefix->value, ref->name.value);
|
||||
}
|
||||
else
|
||||
{
|
||||
alias = scope->lookupType(ref->name.value);
|
||||
}
|
||||
|
||||
if (alias.has_value())
|
||||
{
|
||||
// If the alias is not generic, we don't need to set up a blocked
|
||||
// type and an instantiation constraint.
|
||||
|
@ -1586,7 +1602,11 @@ TypeId ConstraintGraphBuilder::resolveType(const ScopePtr& scope, AstType* ty, b
|
|||
}
|
||||
else
|
||||
{
|
||||
reportError(ty->location, UnknownSymbol{ref->name.value, UnknownSymbol::Context::Type});
|
||||
std::string typeName;
|
||||
if (ref->prefix)
|
||||
typeName = std::string(ref->prefix->value) + ".";
|
||||
typeName += ref->name.value;
|
||||
|
||||
result = singletonTypes->errorRecoveryType();
|
||||
}
|
||||
}
|
||||
|
@ -1685,7 +1705,7 @@ TypeId ConstraintGraphBuilder::resolveType(const ScopePtr& scope, AstType* ty, b
|
|||
else if (auto tof = ty->as<AstTypeTypeof>())
|
||||
{
|
||||
// TODO: Recursion limit.
|
||||
TypeId exprType = check(scope, tof->expr);
|
||||
TypeId exprType = check(scope, tof->expr).ty;
|
||||
result = exprType;
|
||||
}
|
||||
else if (auto unionAnnotation = ty->as<AstTypeUnion>())
|
||||
|
@ -1694,7 +1714,7 @@ TypeId ConstraintGraphBuilder::resolveType(const ScopePtr& scope, AstType* ty, b
|
|||
for (AstType* part : unionAnnotation->types)
|
||||
{
|
||||
// TODO: Recursion limit.
|
||||
parts.push_back(resolveType(scope, part));
|
||||
parts.push_back(resolveType(scope, part, topLevel));
|
||||
}
|
||||
|
||||
result = arena->addType(UnionTypeVar{parts});
|
||||
|
@ -1705,7 +1725,7 @@ TypeId ConstraintGraphBuilder::resolveType(const ScopePtr& scope, AstType* ty, b
|
|||
for (AstType* part : intersectionAnnotation->types)
|
||||
{
|
||||
// TODO: Recursion limit.
|
||||
parts.push_back(resolveType(scope, part));
|
||||
parts.push_back(resolveType(scope, part, topLevel));
|
||||
}
|
||||
|
||||
result = arena->addType(IntersectionTypeVar{parts});
|
||||
|
@ -1795,10 +1815,7 @@ std::vector<std::pair<Name, GenericTypeDefinition>> ConstraintGraphBuilder::crea
|
|||
if (generic.defaultValue)
|
||||
defaultTy = resolveType(scope, generic.defaultValue);
|
||||
|
||||
result.push_back({generic.name.value, GenericTypeDefinition{
|
||||
genericTy,
|
||||
defaultTy,
|
||||
}});
|
||||
result.push_back({generic.name.value, GenericTypeDefinition{genericTy, defaultTy}});
|
||||
}
|
||||
|
||||
return result;
|
||||
|
@ -1816,19 +1833,17 @@ std::vector<std::pair<Name, GenericTypePackDefinition>> ConstraintGraphBuilder::
|
|||
if (generic.defaultValue)
|
||||
defaultTy = resolveTypePack(scope, generic.defaultValue);
|
||||
|
||||
result.push_back({generic.name.value, GenericTypePackDefinition{
|
||||
genericTy,
|
||||
defaultTy,
|
||||
}});
|
||||
result.push_back({generic.name.value, GenericTypePackDefinition{genericTy, defaultTy}});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
TypeId ConstraintGraphBuilder::flattenPack(const ScopePtr& scope, Location location, TypePackId tp)
|
||||
Inference ConstraintGraphBuilder::flattenPack(const ScopePtr& scope, Location location, InferencePack pack)
|
||||
{
|
||||
auto [tp] = pack;
|
||||
if (auto f = first(tp))
|
||||
return *f;
|
||||
return Inference{*f};
|
||||
|
||||
TypeId typeResult = freshType(scope);
|
||||
TypePack onePack{{typeResult}, freshTypePack(scope)};
|
||||
|
@ -1836,7 +1851,7 @@ TypeId ConstraintGraphBuilder::flattenPack(const ScopePtr& scope, Location locat
|
|||
|
||||
addConstraint(scope, location, PackSubtypeConstraint{tp, oneTypePack});
|
||||
|
||||
return typeResult;
|
||||
return Inference{typeResult};
|
||||
}
|
||||
|
||||
void ConstraintGraphBuilder::reportError(Location location, TypeErrorData err)
|
||||
|
|
|
@ -544,6 +544,7 @@ bool ConstraintSolver::tryDispatch(const UnaryConstraint& c, NotNull<const Const
|
|||
}
|
||||
case AstExprUnary::Len:
|
||||
{
|
||||
// __len must return a number.
|
||||
asMutable(c.resultType)->ty.emplace<BoundTypeVar>(singletonTypes->numberType);
|
||||
return true;
|
||||
}
|
||||
|
@ -552,13 +553,46 @@ bool ConstraintSolver::tryDispatch(const UnaryConstraint& c, NotNull<const Const
|
|||
if (isNumber(operandType) || get<AnyTypeVar>(operandType) || get<ErrorTypeVar>(operandType))
|
||||
{
|
||||
asMutable(c.resultType)->ty.emplace<BoundTypeVar>(c.operandType);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
else if (std::optional<TypeId> mm = findMetatableEntry(singletonTypes, errors, operandType, "__unm", constraint->location))
|
||||
{
|
||||
const FunctionTypeVar* ftv = get<FunctionTypeVar>(follow(*mm));
|
||||
|
||||
if (!ftv)
|
||||
{
|
||||
if (std::optional<TypeId> callMm = findMetatableEntry(singletonTypes, errors, follow(*mm), "__call", constraint->location))
|
||||
{
|
||||
ftv = get<FunctionTypeVar>(follow(*callMm));
|
||||
}
|
||||
}
|
||||
|
||||
if (!ftv)
|
||||
{
|
||||
asMutable(c.resultType)->ty.emplace<BoundTypeVar>(singletonTypes->errorRecoveryType());
|
||||
return true;
|
||||
}
|
||||
|
||||
TypePackId argsPack = arena->addTypePack({operandType});
|
||||
unify(ftv->argTypes, argsPack, constraint->scope);
|
||||
|
||||
TypeId result = singletonTypes->errorRecoveryType();
|
||||
if (ftv)
|
||||
{
|
||||
result = first(ftv->retTypes).value_or(singletonTypes->errorRecoveryType());
|
||||
}
|
||||
|
||||
asMutable(c.resultType)->ty.emplace<BoundTypeVar>(result);
|
||||
}
|
||||
else
|
||||
{
|
||||
asMutable(c.resultType)->ty.emplace<BoundTypeVar>(singletonTypes->errorRecoveryType());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
LUAU_ASSERT(false); // TODO metatable handling
|
||||
LUAU_ASSERT(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -862,6 +896,10 @@ bool ConstraintSolver::tryDispatch(const NameConstraint& c, NotNull<const Constr
|
|||
ttv->name = c.name;
|
||||
else if (MetatableTypeVar* mtv = getMutable<MetatableTypeVar>(target))
|
||||
mtv->syntheticName = c.name;
|
||||
else if (get<IntersectionTypeVar>(target) || get<UnionTypeVar>(target))
|
||||
{
|
||||
// nothing (yet)
|
||||
}
|
||||
else
|
||||
return block(c.namedType, constraint);
|
||||
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
#include <stdexcept>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauIceExceptionInheritanceChange, false)
|
||||
|
||||
static std::string wrongNumberOfArgsString(
|
||||
size_t expectedCount, std::optional<size_t> maximumCount, size_t actualCount, const char* argPrefix = nullptr, bool isVariadic = false)
|
||||
{
|
||||
|
@ -460,6 +462,11 @@ struct ErrorConverter
|
|||
{
|
||||
return "Code is too complex to typecheck! Consider simplifying the code around this area";
|
||||
}
|
||||
|
||||
std::string operator()(const TypePackMismatch& e) const
|
||||
{
|
||||
return "Type pack '" + toString(e.givenTp) + "' could not be converted into '" + toString(e.wantedTp) + "'";
|
||||
}
|
||||
};
|
||||
|
||||
struct InvalidNameChecker
|
||||
|
@ -718,6 +725,11 @@ bool TypesAreUnrelated::operator==(const TypesAreUnrelated& rhs) const
|
|||
return left == rhs.left && right == rhs.right;
|
||||
}
|
||||
|
||||
bool TypePackMismatch::operator==(const TypePackMismatch& rhs) const
|
||||
{
|
||||
return *wantedTp == *rhs.wantedTp && *givenTp == *rhs.givenTp;
|
||||
}
|
||||
|
||||
std::string toString(const TypeError& error)
|
||||
{
|
||||
return toString(error, TypeErrorToStringOptions{});
|
||||
|
@ -869,6 +881,11 @@ void copyError(T& e, TypeArena& destArena, CloneState cloneState)
|
|||
else if constexpr (std::is_same_v<T, NormalizationTooComplex>)
|
||||
{
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, TypePackMismatch>)
|
||||
{
|
||||
e.wantedTp = clone(e.wantedTp);
|
||||
e.givenTp = clone(e.givenTp);
|
||||
}
|
||||
else
|
||||
static_assert(always_false_v<T>, "Non-exhaustive type switch");
|
||||
}
|
||||
|
@ -913,4 +930,30 @@ const char* InternalCompilerError::what() const throw()
|
|||
return this->message.data();
|
||||
}
|
||||
|
||||
// TODO: Inline me when LuauIceExceptionInheritanceChange is deleted.
|
||||
void throwRuntimeError(const std::string& message)
|
||||
{
|
||||
if (FFlag::LuauIceExceptionInheritanceChange)
|
||||
{
|
||||
throw InternalCompilerError(message);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::runtime_error(message);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Inline me when LuauIceExceptionInheritanceChange is deleted.
|
||||
void throwRuntimeError(const std::string& message, const std::string& moduleName)
|
||||
{
|
||||
if (FFlag::LuauIceExceptionInheritanceChange)
|
||||
{
|
||||
throw InternalCompilerError(message, moduleName);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::runtime_error(message);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Luau
|
||||
|
|
|
@ -31,6 +31,7 @@ LUAU_FASTINTVARIABLE(LuauAutocompleteCheckTimeoutMs, 100)
|
|||
LUAU_FASTFLAGVARIABLE(DebugLuauDeferredConstraintResolution, false)
|
||||
LUAU_FASTFLAG(DebugLuauLogSolverToJson);
|
||||
LUAU_FASTFLAGVARIABLE(LuauFixMarkDirtyReverseDeps, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauPersistTypesAfterGeneratingDocSyms, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -111,24 +112,57 @@ LoadDefinitionFileResult Frontend::loadDefinitionFile(std::string_view source, c
|
|||
|
||||
CloneState cloneState;
|
||||
|
||||
for (const auto& [name, ty] : checkedModule->declaredGlobals)
|
||||
if (FFlag::LuauPersistTypesAfterGeneratingDocSyms)
|
||||
{
|
||||
TypeId globalTy = clone(ty, globalTypes, cloneState);
|
||||
std::string documentationSymbol = packageName + "/global/" + name;
|
||||
generateDocumentationSymbols(globalTy, documentationSymbol);
|
||||
globalScope->bindings[typeChecker.globalNames.names->getOrAdd(name.c_str())] = {globalTy, Location(), false, {}, documentationSymbol};
|
||||
std::vector<TypeId> typesToPersist;
|
||||
typesToPersist.reserve(checkedModule->declaredGlobals.size() + checkedModule->getModuleScope()->exportedTypeBindings.size());
|
||||
|
||||
persist(globalTy);
|
||||
for (const auto& [name, ty] : checkedModule->declaredGlobals)
|
||||
{
|
||||
TypeId globalTy = clone(ty, globalTypes, cloneState);
|
||||
std::string documentationSymbol = packageName + "/global/" + name;
|
||||
generateDocumentationSymbols(globalTy, documentationSymbol);
|
||||
globalScope->bindings[typeChecker.globalNames.names->getOrAdd(name.c_str())] = {globalTy, Location(), false, {}, documentationSymbol};
|
||||
|
||||
typesToPersist.push_back(globalTy);
|
||||
}
|
||||
|
||||
for (const auto& [name, ty] : checkedModule->getModuleScope()->exportedTypeBindings)
|
||||
{
|
||||
TypeFun globalTy = clone(ty, globalTypes, cloneState);
|
||||
std::string documentationSymbol = packageName + "/globaltype/" + name;
|
||||
generateDocumentationSymbols(globalTy.type, documentationSymbol);
|
||||
globalScope->exportedTypeBindings[name] = globalTy;
|
||||
|
||||
typesToPersist.push_back(globalTy.type);
|
||||
}
|
||||
|
||||
for (TypeId ty : typesToPersist)
|
||||
{
|
||||
persist(ty);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& [name, ty] : checkedModule->getModuleScope()->exportedTypeBindings)
|
||||
else
|
||||
{
|
||||
TypeFun globalTy = clone(ty, globalTypes, cloneState);
|
||||
std::string documentationSymbol = packageName + "/globaltype/" + name;
|
||||
generateDocumentationSymbols(globalTy.type, documentationSymbol);
|
||||
globalScope->exportedTypeBindings[name] = globalTy;
|
||||
for (const auto& [name, ty] : checkedModule->declaredGlobals)
|
||||
{
|
||||
TypeId globalTy = clone(ty, globalTypes, cloneState);
|
||||
std::string documentationSymbol = packageName + "/global/" + name;
|
||||
generateDocumentationSymbols(globalTy, documentationSymbol);
|
||||
globalScope->bindings[typeChecker.globalNames.names->getOrAdd(name.c_str())] = {globalTy, Location(), false, {}, documentationSymbol};
|
||||
|
||||
persist(globalTy.type);
|
||||
persist(globalTy);
|
||||
}
|
||||
|
||||
for (const auto& [name, ty] : checkedModule->getModuleScope()->exportedTypeBindings)
|
||||
{
|
||||
TypeFun globalTy = clone(ty, globalTypes, cloneState);
|
||||
std::string documentationSymbol = packageName + "/globaltype/" + name;
|
||||
generateDocumentationSymbols(globalTy.type, documentationSymbol);
|
||||
globalScope->exportedTypeBindings[name] = globalTy;
|
||||
|
||||
persist(globalTy.type);
|
||||
}
|
||||
}
|
||||
|
||||
return LoadDefinitionFileResult{true, parseResult, checkedModule};
|
||||
|
@ -160,24 +194,57 @@ LoadDefinitionFileResult loadDefinitionFile(TypeChecker& typeChecker, ScopePtr t
|
|||
|
||||
CloneState cloneState;
|
||||
|
||||
for (const auto& [name, ty] : checkedModule->declaredGlobals)
|
||||
if (FFlag::LuauPersistTypesAfterGeneratingDocSyms)
|
||||
{
|
||||
TypeId globalTy = clone(ty, typeChecker.globalTypes, cloneState);
|
||||
std::string documentationSymbol = packageName + "/global/" + name;
|
||||
generateDocumentationSymbols(globalTy, documentationSymbol);
|
||||
targetScope->bindings[typeChecker.globalNames.names->getOrAdd(name.c_str())] = {globalTy, Location(), false, {}, documentationSymbol};
|
||||
std::vector<TypeId> typesToPersist;
|
||||
typesToPersist.reserve(checkedModule->declaredGlobals.size() + checkedModule->getModuleScope()->exportedTypeBindings.size());
|
||||
|
||||
persist(globalTy);
|
||||
for (const auto& [name, ty] : checkedModule->declaredGlobals)
|
||||
{
|
||||
TypeId globalTy = clone(ty, typeChecker.globalTypes, cloneState);
|
||||
std::string documentationSymbol = packageName + "/global/" + name;
|
||||
generateDocumentationSymbols(globalTy, documentationSymbol);
|
||||
targetScope->bindings[typeChecker.globalNames.names->getOrAdd(name.c_str())] = {globalTy, Location(), false, {}, documentationSymbol};
|
||||
|
||||
typesToPersist.push_back(globalTy);
|
||||
}
|
||||
|
||||
for (const auto& [name, ty] : checkedModule->getModuleScope()->exportedTypeBindings)
|
||||
{
|
||||
TypeFun globalTy = clone(ty, typeChecker.globalTypes, cloneState);
|
||||
std::string documentationSymbol = packageName + "/globaltype/" + name;
|
||||
generateDocumentationSymbols(globalTy.type, documentationSymbol);
|
||||
targetScope->exportedTypeBindings[name] = globalTy;
|
||||
|
||||
typesToPersist.push_back(globalTy.type);
|
||||
}
|
||||
|
||||
for (TypeId ty : typesToPersist)
|
||||
{
|
||||
persist(ty);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& [name, ty] : checkedModule->getModuleScope()->exportedTypeBindings)
|
||||
else
|
||||
{
|
||||
TypeFun globalTy = clone(ty, typeChecker.globalTypes, cloneState);
|
||||
std::string documentationSymbol = packageName + "/globaltype/" + name;
|
||||
generateDocumentationSymbols(globalTy.type, documentationSymbol);
|
||||
targetScope->exportedTypeBindings[name] = globalTy;
|
||||
for (const auto& [name, ty] : checkedModule->declaredGlobals)
|
||||
{
|
||||
TypeId globalTy = clone(ty, typeChecker.globalTypes, cloneState);
|
||||
std::string documentationSymbol = packageName + "/global/" + name;
|
||||
generateDocumentationSymbols(globalTy, documentationSymbol);
|
||||
targetScope->bindings[typeChecker.globalNames.names->getOrAdd(name.c_str())] = {globalTy, Location(), false, {}, documentationSymbol};
|
||||
|
||||
persist(globalTy.type);
|
||||
persist(globalTy);
|
||||
}
|
||||
|
||||
for (const auto& [name, ty] : checkedModule->getModuleScope()->exportedTypeBindings)
|
||||
{
|
||||
TypeFun globalTy = clone(ty, typeChecker.globalTypes, cloneState);
|
||||
std::string documentationSymbol = packageName + "/globaltype/" + name;
|
||||
generateDocumentationSymbols(globalTy.type, documentationSymbol);
|
||||
targetScope->exportedTypeBindings[name] = globalTy;
|
||||
|
||||
persist(globalTy.type);
|
||||
}
|
||||
}
|
||||
|
||||
return LoadDefinitionFileResult{true, parseResult, checkedModule};
|
||||
|
@ -426,13 +493,13 @@ CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOption
|
|||
{
|
||||
auto it2 = moduleResolverForAutocomplete.modules.find(name);
|
||||
if (it2 == moduleResolverForAutocomplete.modules.end() || it2->second == nullptr)
|
||||
throw std::runtime_error("Frontend::modules does not have data for " + name);
|
||||
throwRuntimeError("Frontend::modules does not have data for " + name, name);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto it2 = moduleResolver.modules.find(name);
|
||||
if (it2 == moduleResolver.modules.end() || it2->second == nullptr)
|
||||
throw std::runtime_error("Frontend::modules does not have data for " + name);
|
||||
throwRuntimeError("Frontend::modules does not have data for " + name, name);
|
||||
}
|
||||
|
||||
return CheckResult{
|
||||
|
@ -539,7 +606,7 @@ CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOption
|
|||
stats.filesNonstrict += mode == Mode::Nonstrict;
|
||||
|
||||
if (module == nullptr)
|
||||
throw std::runtime_error("Frontend::check produced a nullptr module for " + moduleName);
|
||||
throwRuntimeError("Frontend::check produced a nullptr module for " + moduleName, moduleName);
|
||||
|
||||
if (!frontendOptions.retainFullTypeGraphs)
|
||||
{
|
||||
|
@ -1007,11 +1074,11 @@ SourceModule Frontend::parse(const ModuleName& name, std::string_view src, const
|
|||
|
||||
double timestamp = getTimestamp();
|
||||
|
||||
auto parseResult = Luau::Parser::parse(src.data(), src.size(), *sourceModule.names, *sourceModule.allocator, parseOptions);
|
||||
Luau::ParseResult parseResult = Luau::Parser::parse(src.data(), src.size(), *sourceModule.names, *sourceModule.allocator, parseOptions);
|
||||
|
||||
stats.timeParse += getTimestamp() - timestamp;
|
||||
stats.files++;
|
||||
stats.lines += std::count(src.begin(), src.end(), '\n') + (src.size() && src.back() != '\n');
|
||||
stats.lines += parseResult.lines;
|
||||
|
||||
if (!parseResult.errors.empty())
|
||||
sourceModule.parseErrors.insert(sourceModule.parseErrors.end(), parseResult.errors.begin(), parseResult.errors.end());
|
||||
|
|
|
@ -188,6 +188,8 @@ static void errorToString(std::ostream& stream, const T& err)
|
|||
stream << "TypesAreUnrelated { left = '" + toString(err.left) + "', right = '" + toString(err.right) + "' }";
|
||||
else if constexpr (std::is_same_v<T, NormalizationTooComplex>)
|
||||
stream << "NormalizationTooComplex { }";
|
||||
else if constexpr (std::is_same_v<T, TypePackMismatch>)
|
||||
stream << "TypePackMismatch { wanted = '" + toString(err.wantedTp) + "', given = '" + toString(err.givenTp) + "' }";
|
||||
else
|
||||
static_assert(always_false_v<T>, "Non-exhaustive type switch");
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
#include "Luau/Clone.h"
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/TypeVar.h"
|
||||
#include "Luau/Unifier.h"
|
||||
#include "Luau/VisitTypeVar.h"
|
||||
|
||||
|
@ -18,6 +19,7 @@ LUAU_FASTINTVARIABLE(LuauNormalizeIterationLimit, 1200);
|
|||
LUAU_FASTINTVARIABLE(LuauNormalizeCacheLimit, 100000);
|
||||
LUAU_FASTFLAGVARIABLE(LuauNormalizeCombineTableFix, false);
|
||||
LUAU_FASTFLAGVARIABLE(LuauTypeNormalization2, false);
|
||||
LUAU_FASTFLAGVARIABLE(LuauNegatedStringSingletons, false);
|
||||
LUAU_FASTFLAG(LuauUnknownAndNeverType)
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||
LUAU_FASTFLAG(LuauOverloadedFunctionSubtypingPerf);
|
||||
|
@ -107,12 +109,110 @@ bool TypeIds::operator==(const TypeIds& there) const
|
|||
return hash == there.hash && types == there.types;
|
||||
}
|
||||
|
||||
NormalizedStringType::NormalizedStringType(bool isCofinite, std::optional<std::map<std::string, TypeId>> singletons)
|
||||
: isCofinite(isCofinite)
|
||||
, singletons(std::move(singletons))
|
||||
{
|
||||
if (!FFlag::LuauNegatedStringSingletons)
|
||||
LUAU_ASSERT(!isCofinite);
|
||||
}
|
||||
|
||||
void NormalizedStringType::resetToString()
|
||||
{
|
||||
if (FFlag::LuauNegatedStringSingletons)
|
||||
{
|
||||
isCofinite = true;
|
||||
singletons->clear();
|
||||
}
|
||||
else
|
||||
singletons.reset();
|
||||
}
|
||||
|
||||
void NormalizedStringType::resetToNever()
|
||||
{
|
||||
if (FFlag::LuauNegatedStringSingletons)
|
||||
{
|
||||
isCofinite = false;
|
||||
singletons.emplace();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (singletons)
|
||||
singletons->clear();
|
||||
else
|
||||
singletons.emplace();
|
||||
}
|
||||
}
|
||||
|
||||
bool NormalizedStringType::isNever() const
|
||||
{
|
||||
if (FFlag::LuauNegatedStringSingletons)
|
||||
return !isCofinite && singletons->empty();
|
||||
else
|
||||
return singletons && singletons->empty();
|
||||
}
|
||||
|
||||
bool NormalizedStringType::isString() const
|
||||
{
|
||||
if (FFlag::LuauNegatedStringSingletons)
|
||||
return isCofinite && singletons->empty();
|
||||
else
|
||||
return !singletons;
|
||||
}
|
||||
|
||||
bool NormalizedStringType::isUnion() const
|
||||
{
|
||||
if (FFlag::LuauNegatedStringSingletons)
|
||||
return !isCofinite;
|
||||
else
|
||||
return singletons.has_value();
|
||||
}
|
||||
|
||||
bool NormalizedStringType::isIntersection() const
|
||||
{
|
||||
if (FFlag::LuauNegatedStringSingletons)
|
||||
return isCofinite;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NormalizedStringType::includes(const std::string& str) const
|
||||
{
|
||||
if (isString())
|
||||
return true;
|
||||
else if (isUnion() && singletons->count(str))
|
||||
return true;
|
||||
else if (isIntersection() && !singletons->count(str))
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
const NormalizedStringType NormalizedStringType::never{false, {{}}};
|
||||
|
||||
bool isSubtype(const NormalizedStringType& subStr, const NormalizedStringType& superStr)
|
||||
{
|
||||
if (subStr.isUnion() && superStr.isUnion())
|
||||
{
|
||||
for (auto [name, ty] : *subStr.singletons)
|
||||
{
|
||||
if (!superStr.singletons->count(name))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (subStr.isString() && superStr.isUnion())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
NormalizedType::NormalizedType(NotNull<SingletonTypes> singletonTypes)
|
||||
: tops(singletonTypes->neverType)
|
||||
, booleans(singletonTypes->neverType)
|
||||
, errors(singletonTypes->neverType)
|
||||
, nils(singletonTypes->neverType)
|
||||
, numbers(singletonTypes->neverType)
|
||||
, strings{NormalizedStringType::never}
|
||||
, threads(singletonTypes->neverType)
|
||||
{
|
||||
}
|
||||
|
@ -120,7 +220,7 @@ NormalizedType::NormalizedType(NotNull<SingletonTypes> singletonTypes)
|
|||
static bool isInhabited(const NormalizedType& norm)
|
||||
{
|
||||
return !get<NeverTypeVar>(norm.tops) || !get<NeverTypeVar>(norm.booleans) || !norm.classes.empty() || !get<NeverTypeVar>(norm.errors) ||
|
||||
!get<NeverTypeVar>(norm.nils) || !get<NeverTypeVar>(norm.numbers) || !norm.strings || !norm.strings->empty() ||
|
||||
!get<NeverTypeVar>(norm.nils) || !get<NeverTypeVar>(norm.numbers) || !norm.strings.isNever() ||
|
||||
!get<NeverTypeVar>(norm.threads) || norm.functions || !norm.tables.empty() || !norm.tyvars.empty();
|
||||
}
|
||||
|
||||
|
@ -183,10 +283,10 @@ static bool isNormalizedNumber(TypeId ty)
|
|||
|
||||
static bool isNormalizedString(const NormalizedStringType& ty)
|
||||
{
|
||||
if (!ty)
|
||||
if (ty.isString())
|
||||
return true;
|
||||
|
||||
for (auto& [str, ty] : *ty)
|
||||
for (auto& [str, ty] : *ty.singletons)
|
||||
{
|
||||
if (const SingletonTypeVar* stv = get<SingletonTypeVar>(ty))
|
||||
{
|
||||
|
@ -317,10 +417,7 @@ void Normalizer::clearNormal(NormalizedType& norm)
|
|||
norm.errors = singletonTypes->neverType;
|
||||
norm.nils = singletonTypes->neverType;
|
||||
norm.numbers = singletonTypes->neverType;
|
||||
if (norm.strings)
|
||||
norm.strings->clear();
|
||||
else
|
||||
norm.strings.emplace();
|
||||
norm.strings.resetToNever();
|
||||
norm.threads = singletonTypes->neverType;
|
||||
norm.tables.clear();
|
||||
norm.functions = std::nullopt;
|
||||
|
@ -495,10 +592,56 @@ void Normalizer::unionClasses(TypeIds& heres, const TypeIds& theres)
|
|||
|
||||
void Normalizer::unionStrings(NormalizedStringType& here, const NormalizedStringType& there)
|
||||
{
|
||||
if (!there)
|
||||
here.reset();
|
||||
else if (here)
|
||||
here->insert(there->begin(), there->end());
|
||||
if (FFlag::LuauNegatedStringSingletons)
|
||||
{
|
||||
if (there.isString())
|
||||
here.resetToString();
|
||||
else if (here.isUnion() && there.isUnion())
|
||||
here.singletons->insert(there.singletons->begin(), there.singletons->end());
|
||||
else if (here.isUnion() && there.isIntersection())
|
||||
{
|
||||
here.isCofinite = true;
|
||||
for (const auto& pair : *there.singletons)
|
||||
{
|
||||
auto it = here.singletons->find(pair.first);
|
||||
if (it != end(*here.singletons))
|
||||
here.singletons->erase(it);
|
||||
else
|
||||
here.singletons->insert(pair);
|
||||
}
|
||||
}
|
||||
else if (here.isIntersection() && there.isUnion())
|
||||
{
|
||||
for (const auto& [name, ty] : *there.singletons)
|
||||
here.singletons->erase(name);
|
||||
}
|
||||
else if (here.isIntersection() && there.isIntersection())
|
||||
{
|
||||
auto iter = begin(*here.singletons);
|
||||
auto endIter = end(*here.singletons);
|
||||
|
||||
while (iter != endIter)
|
||||
{
|
||||
if (!there.singletons->count(iter->first))
|
||||
{
|
||||
auto eraseIt = iter;
|
||||
++iter;
|
||||
here.singletons->erase(eraseIt);
|
||||
}
|
||||
else
|
||||
++iter;
|
||||
}
|
||||
}
|
||||
else
|
||||
LUAU_ASSERT(!"Unreachable");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (there.isString())
|
||||
here.resetToString();
|
||||
else if (here.isUnion())
|
||||
here.singletons->insert(there.singletons->begin(), there.singletons->end());
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<TypePackId> Normalizer::unionOfTypePacks(TypePackId here, TypePackId there)
|
||||
|
@ -858,7 +1001,7 @@ bool Normalizer::unionNormalWithTy(NormalizedType& here, TypeId there, int ignor
|
|||
else if (ptv->type == PrimitiveTypeVar::Number)
|
||||
here.numbers = there;
|
||||
else if (ptv->type == PrimitiveTypeVar::String)
|
||||
here.strings = std::nullopt;
|
||||
here.strings.resetToString();
|
||||
else if (ptv->type == PrimitiveTypeVar::Thread)
|
||||
here.threads = there;
|
||||
else
|
||||
|
@ -870,12 +1013,33 @@ bool Normalizer::unionNormalWithTy(NormalizedType& here, TypeId there, int ignor
|
|||
here.booleans = unionOfBools(here.booleans, there);
|
||||
else if (const StringSingleton* sstv = get<StringSingleton>(stv))
|
||||
{
|
||||
if (here.strings)
|
||||
here.strings->insert({sstv->value, there});
|
||||
if (FFlag::LuauNegatedStringSingletons)
|
||||
{
|
||||
if (here.strings.isCofinite)
|
||||
{
|
||||
auto it = here.strings.singletons->find(sstv->value);
|
||||
if (it != here.strings.singletons->end())
|
||||
here.strings.singletons->erase(it);
|
||||
}
|
||||
else
|
||||
here.strings.singletons->insert({sstv->value, there});
|
||||
}
|
||||
else
|
||||
{
|
||||
if (here.strings.isUnion())
|
||||
here.strings.singletons->insert({sstv->value, there});
|
||||
}
|
||||
}
|
||||
else
|
||||
LUAU_ASSERT(!"Unreachable");
|
||||
}
|
||||
else if (const NegationTypeVar* ntv = get<NegationTypeVar>(there))
|
||||
{
|
||||
const NormalizedType* thereNormal = normalize(ntv->ty);
|
||||
NormalizedType tn = negateNormal(*thereNormal);
|
||||
if (!unionNormals(here, tn))
|
||||
return false;
|
||||
}
|
||||
else
|
||||
LUAU_ASSERT(!"Unreachable");
|
||||
|
||||
|
@ -887,6 +1051,159 @@ bool Normalizer::unionNormalWithTy(NormalizedType& here, TypeId there, int ignor
|
|||
return true;
|
||||
}
|
||||
|
||||
// ------- Negations
|
||||
|
||||
NormalizedType Normalizer::negateNormal(const NormalizedType& here)
|
||||
{
|
||||
NormalizedType result{singletonTypes};
|
||||
if (!get<NeverTypeVar>(here.tops))
|
||||
{
|
||||
// The negation of unknown or any is never. Easy.
|
||||
return result;
|
||||
}
|
||||
|
||||
if (!get<NeverTypeVar>(here.errors))
|
||||
{
|
||||
// Negating an error yields the same error.
|
||||
result.errors = here.errors;
|
||||
return result;
|
||||
}
|
||||
|
||||
if (get<NeverTypeVar>(here.booleans))
|
||||
result.booleans = singletonTypes->booleanType;
|
||||
else if (get<PrimitiveTypeVar>(here.booleans))
|
||||
result.booleans = singletonTypes->neverType;
|
||||
else if (auto stv = get<SingletonTypeVar>(here.booleans))
|
||||
{
|
||||
auto boolean = get<BooleanSingleton>(stv);
|
||||
LUAU_ASSERT(boolean != nullptr);
|
||||
if (boolean->value)
|
||||
result.booleans = singletonTypes->falseType;
|
||||
else
|
||||
result.booleans = singletonTypes->trueType;
|
||||
}
|
||||
|
||||
result.classes = negateAll(here.classes);
|
||||
result.nils = get<NeverTypeVar>(here.nils) ? singletonTypes->nilType : singletonTypes->neverType;
|
||||
result.numbers = get<NeverTypeVar>(here.numbers) ? singletonTypes->numberType : singletonTypes->neverType;
|
||||
|
||||
result.strings = here.strings;
|
||||
result.strings.isCofinite = !result.strings.isCofinite;
|
||||
|
||||
result.threads = get<NeverTypeVar>(here.threads) ? singletonTypes->threadType : singletonTypes->neverType;
|
||||
|
||||
// TODO: negating tables
|
||||
// TODO: negating functions
|
||||
// TODO: negating tyvars?
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
TypeIds Normalizer::negateAll(const TypeIds& theres)
|
||||
{
|
||||
TypeIds tys;
|
||||
for (TypeId there : theres)
|
||||
tys.insert(negate(there));
|
||||
return tys;
|
||||
}
|
||||
|
||||
TypeId Normalizer::negate(TypeId there)
|
||||
{
|
||||
there = follow(there);
|
||||
if (get<AnyTypeVar>(there))
|
||||
return there;
|
||||
else if (get<UnknownTypeVar>(there))
|
||||
return singletonTypes->neverType;
|
||||
else if (get<NeverTypeVar>(there))
|
||||
return singletonTypes->unknownType;
|
||||
else if (auto ntv = get<NegationTypeVar>(there))
|
||||
return ntv->ty; // TODO: do we want to normalize this?
|
||||
else if (auto utv = get<UnionTypeVar>(there))
|
||||
{
|
||||
std::vector<TypeId> parts;
|
||||
for (TypeId option : utv)
|
||||
parts.push_back(negate(option));
|
||||
return arena->addType(IntersectionTypeVar{std::move(parts)});
|
||||
}
|
||||
else if (auto itv = get<IntersectionTypeVar>(there))
|
||||
{
|
||||
std::vector<TypeId> options;
|
||||
for (TypeId part : itv)
|
||||
options.push_back(negate(part));
|
||||
return arena->addType(UnionTypeVar{std::move(options)});
|
||||
}
|
||||
else
|
||||
return there;
|
||||
}
|
||||
|
||||
void Normalizer::subtractPrimitive(NormalizedType& here, TypeId ty)
|
||||
{
|
||||
const PrimitiveTypeVar* ptv = get<PrimitiveTypeVar>(follow(ty));
|
||||
LUAU_ASSERT(ptv);
|
||||
switch (ptv->type)
|
||||
{
|
||||
case PrimitiveTypeVar::NilType:
|
||||
here.nils = singletonTypes->neverType;
|
||||
break;
|
||||
case PrimitiveTypeVar::Boolean:
|
||||
here.booleans = singletonTypes->neverType;
|
||||
break;
|
||||
case PrimitiveTypeVar::Number:
|
||||
here.numbers = singletonTypes->neverType;
|
||||
break;
|
||||
case PrimitiveTypeVar::String:
|
||||
here.strings.resetToNever();
|
||||
break;
|
||||
case PrimitiveTypeVar::Thread:
|
||||
here.threads = singletonTypes->neverType;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Normalizer::subtractSingleton(NormalizedType& here, TypeId ty)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauNegatedStringSingletons);
|
||||
|
||||
const SingletonTypeVar* stv = get<SingletonTypeVar>(ty);
|
||||
LUAU_ASSERT(stv);
|
||||
|
||||
if (const StringSingleton* ss = get<StringSingleton>(stv))
|
||||
{
|
||||
if (here.strings.isCofinite)
|
||||
here.strings.singletons->insert({ss->value, ty});
|
||||
else
|
||||
{
|
||||
auto it = here.strings.singletons->find(ss->value);
|
||||
if (it != here.strings.singletons->end())
|
||||
here.strings.singletons->erase(it);
|
||||
}
|
||||
}
|
||||
else if (const BooleanSingleton* bs = get<BooleanSingleton>(stv))
|
||||
{
|
||||
if (get<NeverTypeVar>(here.booleans))
|
||||
{
|
||||
// Nothing
|
||||
}
|
||||
else if (get<PrimitiveTypeVar>(here.booleans))
|
||||
here.booleans = bs->value ? singletonTypes->falseType : singletonTypes->trueType;
|
||||
else if (auto hereSingleton = get<SingletonTypeVar>(here.booleans))
|
||||
{
|
||||
const BooleanSingleton* hereBooleanSingleton = get<BooleanSingleton>(hereSingleton);
|
||||
LUAU_ASSERT(hereBooleanSingleton);
|
||||
|
||||
// Crucial subtlety: ty (and thus bs) are the value that is being
|
||||
// negated out. We therefore reduce to never when the values match,
|
||||
// rather than when they differ.
|
||||
if (bs->value == hereBooleanSingleton->value)
|
||||
here.booleans = singletonTypes->neverType;
|
||||
}
|
||||
else
|
||||
LUAU_ASSERT(!"Unreachable");
|
||||
}
|
||||
else
|
||||
LUAU_ASSERT(!"Unreachable");
|
||||
}
|
||||
|
||||
// ------- Normalizing intersections
|
||||
TypeId Normalizer::intersectionOfTops(TypeId here, TypeId there)
|
||||
{
|
||||
|
@ -971,17 +1288,17 @@ void Normalizer::intersectClassesWithClass(TypeIds& heres, TypeId there)
|
|||
|
||||
void Normalizer::intersectStrings(NormalizedStringType& here, const NormalizedStringType& there)
|
||||
{
|
||||
if (!there)
|
||||
if (there.isString())
|
||||
return;
|
||||
if (!here)
|
||||
here.emplace();
|
||||
if (here.isString())
|
||||
here.resetToNever();
|
||||
|
||||
for (auto it = here->begin(); it != here->end();)
|
||||
for (auto it = here.singletons->begin(); it != here.singletons->end();)
|
||||
{
|
||||
if (there->count(it->first))
|
||||
if (there.singletons->count(it->first))
|
||||
it++;
|
||||
else
|
||||
it = here->erase(it);
|
||||
it = here.singletons->erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1646,12 +1963,35 @@ bool Normalizer::intersectNormalWithTy(NormalizedType& here, TypeId there)
|
|||
here.booleans = intersectionOfBools(booleans, there);
|
||||
else if (const StringSingleton* sstv = get<StringSingleton>(stv))
|
||||
{
|
||||
if (!strings || strings->count(sstv->value))
|
||||
here.strings->insert({sstv->value, there});
|
||||
if (strings.includes(sstv->value))
|
||||
here.strings.singletons->insert({sstv->value, there});
|
||||
}
|
||||
else
|
||||
LUAU_ASSERT(!"Unreachable");
|
||||
}
|
||||
else if (const NegationTypeVar* ntv = get<NegationTypeVar>(there); FFlag::LuauNegatedStringSingletons && ntv)
|
||||
{
|
||||
TypeId t = follow(ntv->ty);
|
||||
if (const PrimitiveTypeVar* ptv = get<PrimitiveTypeVar>(t))
|
||||
subtractPrimitive(here, ntv->ty);
|
||||
else if (const SingletonTypeVar* stv = get<SingletonTypeVar>(t))
|
||||
subtractSingleton(here, follow(ntv->ty));
|
||||
else if (const UnionTypeVar* itv = get<UnionTypeVar>(t))
|
||||
{
|
||||
for (TypeId part : itv->options)
|
||||
{
|
||||
const NormalizedType* normalPart = normalize(part);
|
||||
NormalizedType negated = negateNormal(*normalPart);
|
||||
intersectNormals(here, negated);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO negated unions, intersections, table, and function.
|
||||
// Report a TypeError for other types.
|
||||
LUAU_ASSERT(!"Unimplemented");
|
||||
}
|
||||
}
|
||||
else
|
||||
LUAU_ASSERT(!"Unreachable");
|
||||
|
||||
|
@ -1691,11 +2031,25 @@ TypeId Normalizer::typeFromNormal(const NormalizedType& norm)
|
|||
result.push_back(norm.nils);
|
||||
if (!get<NeverTypeVar>(norm.numbers))
|
||||
result.push_back(norm.numbers);
|
||||
if (norm.strings)
|
||||
for (auto& [_, ty] : *norm.strings)
|
||||
result.push_back(ty);
|
||||
else
|
||||
if (norm.strings.isString())
|
||||
result.push_back(singletonTypes->stringType);
|
||||
else if (norm.strings.isUnion())
|
||||
{
|
||||
for (auto& [_, ty] : *norm.strings.singletons)
|
||||
result.push_back(ty);
|
||||
}
|
||||
else if (FFlag::LuauNegatedStringSingletons && norm.strings.isIntersection())
|
||||
{
|
||||
std::vector<TypeId> parts;
|
||||
parts.push_back(singletonTypes->stringType);
|
||||
for (const auto& [name, ty] : *norm.strings.singletons)
|
||||
parts.push_back(arena->addType(NegationTypeVar{ty}));
|
||||
|
||||
result.push_back(arena->addType(IntersectionTypeVar{std::move(parts)}));
|
||||
}
|
||||
if (!get<NeverTypeVar>(norm.threads))
|
||||
result.push_back(singletonTypes->threadType);
|
||||
|
||||
result.insert(result.end(), norm.tables.begin(), norm.tables.end());
|
||||
for (auto& [tyvar, intersect] : norm.tyvars)
|
||||
{
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include <stdexcept>
|
||||
|
||||
LUAU_FASTFLAG(LuauUnknownAndNeverType)
|
||||
LUAU_FASTFLAG(LuauLvaluelessPath)
|
||||
LUAU_FASTFLAGVARIABLE(LuauSpecialTypesAsterisked, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauFixNameMaps, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauUnseeArrayTtv, false)
|
||||
|
@ -435,7 +436,7 @@ struct TypeVarStringifier
|
|||
return;
|
||||
default:
|
||||
LUAU_ASSERT(!"Unknown primitive type");
|
||||
throw std::runtime_error("Unknown primitive type " + std::to_string(ptv.type));
|
||||
throwRuntimeError("Unknown primitive type " + std::to_string(ptv.type));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -452,7 +453,7 @@ struct TypeVarStringifier
|
|||
else
|
||||
{
|
||||
LUAU_ASSERT(!"Unknown singleton type");
|
||||
throw std::runtime_error("Unknown singleton type");
|
||||
throwRuntimeError("Unknown singleton type");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1548,6 +1549,8 @@ std::string dump(const Constraint& c)
|
|||
|
||||
std::string toString(const LValue& lvalue)
|
||||
{
|
||||
LUAU_ASSERT(!FFlag::LuauLvaluelessPath);
|
||||
|
||||
std::string s;
|
||||
for (const LValue* current = &lvalue; current; current = baseof(*current))
|
||||
{
|
||||
|
@ -1562,4 +1565,37 @@ std::string toString(const LValue& lvalue)
|
|||
return s;
|
||||
}
|
||||
|
||||
std::optional<std::string> getFunctionNameAsString(const AstExpr& expr)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauLvaluelessPath);
|
||||
|
||||
const AstExpr* curr = &expr;
|
||||
std::string s;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
if (auto local = curr->as<AstExprLocal>())
|
||||
return local->local->name.value + s;
|
||||
|
||||
if (auto global = curr->as<AstExprGlobal>())
|
||||
return global->name.value + s;
|
||||
|
||||
if (auto indexname = curr->as<AstExprIndexName>())
|
||||
{
|
||||
curr = indexname->expr;
|
||||
|
||||
s = "." + std::string(indexname->index.value) + s;
|
||||
}
|
||||
else if (auto group = curr->as<AstExprGroup>())
|
||||
{
|
||||
curr = group->expr;
|
||||
}
|
||||
else
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
} // namespace Luau
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/TopoSortStatements.h"
|
||||
|
||||
#include "Luau/Error.h"
|
||||
/* Decide the order in which we typecheck Lua statements in a block.
|
||||
*
|
||||
* Algorithm:
|
||||
|
@ -149,7 +150,7 @@ Identifier mkName(const AstStatFunction& function)
|
|||
auto name = mkName(*function.name);
|
||||
LUAU_ASSERT(bool(name));
|
||||
if (!name)
|
||||
throw std::runtime_error("Internal error: Function declaration has a bad name");
|
||||
throwRuntimeError("Internal error: Function declaration has a bad name");
|
||||
|
||||
return *name;
|
||||
}
|
||||
|
@ -255,7 +256,7 @@ struct ArcCollector : public AstVisitor
|
|||
{
|
||||
auto name = mkName(*node->name);
|
||||
if (!name)
|
||||
throw std::runtime_error("Internal error: AstStatFunction has a bad name");
|
||||
throwRuntimeError("Internal error: AstStatFunction has a bad name");
|
||||
|
||||
add(*name);
|
||||
return true;
|
||||
|
|
|
@ -347,7 +347,7 @@ public:
|
|||
AstType* operator()(const NegationTypeVar& ntv)
|
||||
{
|
||||
// FIXME: do the same thing we do with ErrorTypeVar
|
||||
throw std::runtime_error("Cannot convert NegationTypeVar into AstNode");
|
||||
throwRuntimeError("Cannot convert NegationTypeVar into AstNode");
|
||||
}
|
||||
|
||||
private:
|
||||
|
|
|
@ -934,8 +934,62 @@ struct TypeChecker2
|
|||
|
||||
void visit(AstExprUnary* expr)
|
||||
{
|
||||
// TODO!
|
||||
visit(expr->expr);
|
||||
|
||||
NotNull<Scope> scope = stack.back();
|
||||
TypeId operandType = lookupType(expr->expr);
|
||||
|
||||
if (get<AnyTypeVar>(operandType) || get<ErrorTypeVar>(operandType) || get<NeverTypeVar>(operandType))
|
||||
return;
|
||||
|
||||
if (auto it = kUnaryOpMetamethods.find(expr->op); it != kUnaryOpMetamethods.end())
|
||||
{
|
||||
std::optional<TypeId> mm = findMetatableEntry(singletonTypes, module->errors, operandType, it->second, expr->location);
|
||||
if (mm)
|
||||
{
|
||||
if (const FunctionTypeVar* ftv = get<FunctionTypeVar>(follow(*mm)))
|
||||
{
|
||||
TypePackId expectedArgs = module->internalTypes.addTypePack({operandType});
|
||||
reportErrors(tryUnify(scope, expr->location, ftv->argTypes, expectedArgs));
|
||||
|
||||
if (std::optional<TypeId> ret = first(ftv->retTypes))
|
||||
{
|
||||
if (expr->op == AstExprUnary::Op::Len)
|
||||
{
|
||||
reportErrors(tryUnify(scope, expr->location, follow(*ret), singletonTypes->numberType));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
reportError(GenericError{format("Metamethod '%s' must return a value", it->second)}, expr->location);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (expr->op == AstExprUnary::Op::Len)
|
||||
{
|
||||
DenseHashSet<TypeId> seen{nullptr};
|
||||
int recursionCount = 0;
|
||||
|
||||
if (!hasLength(operandType, seen, &recursionCount))
|
||||
{
|
||||
reportError(NotATable{operandType}, expr->location);
|
||||
}
|
||||
}
|
||||
else if (expr->op == AstExprUnary::Op::Minus)
|
||||
{
|
||||
reportErrors(tryUnify(scope, expr->location, operandType, singletonTypes->numberType));
|
||||
}
|
||||
else if (expr->op == AstExprUnary::Op::Not)
|
||||
{
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_ASSERT(!"Unhandled unary operator");
|
||||
}
|
||||
}
|
||||
|
||||
void visit(AstExprBinary* expr)
|
||||
|
@ -1240,9 +1294,8 @@ struct TypeChecker2
|
|||
Scope* scope = findInnermostScope(ty->location);
|
||||
LUAU_ASSERT(scope);
|
||||
|
||||
// TODO: Imported types
|
||||
|
||||
std::optional<TypeFun> alias = scope->lookupType(ty->name.value);
|
||||
std::optional<TypeFun> alias =
|
||||
(ty->prefix) ? scope->lookupImportedType(ty->prefix->value, ty->name.value) : scope->lookupType(ty->name.value);
|
||||
|
||||
if (alias.has_value())
|
||||
{
|
||||
|
|
|
@ -36,6 +36,7 @@ LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false)
|
|||
LUAU_FASTFLAGVARIABLE(LuauReturnAnyInsteadOfICE, false) // Eventually removed as false.
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauSharedSelf, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauAnyifyModuleReturnGenerics, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauLvaluelessPath, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauUnknownAndNeverType, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauBinaryNeedsExpectedTypesToo, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauFixVarargExprHeadType, false)
|
||||
|
@ -43,15 +44,15 @@ LUAU_FASTFLAGVARIABLE(LuauNeverTypesAndOperatorsInference, false)
|
|||
LUAU_FASTFLAGVARIABLE(LuauReturnsFromCallsitesAreNotWidened, false)
|
||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||
LUAU_FASTFLAGVARIABLE(LuauCompleteVisitor, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauUnionOfTypesFollow, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauReportShadowedTypeAlias, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauBetterMessagingOnCountMismatch, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauArgMismatchReportFunctionLocation, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
const char* TimeLimitError::what() const throw()
|
||||
const char* TimeLimitError_DEPRECATED::what() const throw()
|
||||
{
|
||||
LUAU_ASSERT(!FFlag::LuauIceExceptionInheritanceChange);
|
||||
return "Typeinfer failed to complete in allotted time";
|
||||
}
|
||||
|
||||
|
@ -264,6 +265,11 @@ ModulePtr TypeChecker::check(const SourceModule& module, Mode mode, std::optiona
|
|||
reportErrorCodeTooComplex(module.root->location);
|
||||
return std::move(currentModule);
|
||||
}
|
||||
catch (const RecursionLimitException_DEPRECATED&)
|
||||
{
|
||||
reportErrorCodeTooComplex(module.root->location);
|
||||
return std::move(currentModule);
|
||||
}
|
||||
}
|
||||
|
||||
ModulePtr TypeChecker::checkWithoutRecursionCheck(const SourceModule& module, Mode mode, std::optional<ScopePtr> environmentScope)
|
||||
|
@ -308,6 +314,10 @@ ModulePtr TypeChecker::checkWithoutRecursionCheck(const SourceModule& module, Mo
|
|||
{
|
||||
currentModule->timeout = true;
|
||||
}
|
||||
catch (const TimeLimitError_DEPRECATED&)
|
||||
{
|
||||
currentModule->timeout = true;
|
||||
}
|
||||
|
||||
if (FFlag::DebugLuauSharedSelf)
|
||||
{
|
||||
|
@ -415,7 +425,7 @@ void TypeChecker::check(const ScopePtr& scope, const AstStat& program)
|
|||
ice("Unknown AstStat");
|
||||
|
||||
if (finishTime && TimeTrace::getClock() > *finishTime)
|
||||
throw TimeLimitError();
|
||||
throwTimeLimitError();
|
||||
}
|
||||
|
||||
// This particular overload is for do...end. If you need to not increase the scope level, use checkBlock directly.
|
||||
|
@ -442,6 +452,11 @@ void TypeChecker::checkBlock(const ScopePtr& scope, const AstStatBlock& block)
|
|||
reportErrorCodeTooComplex(block.location);
|
||||
return;
|
||||
}
|
||||
catch (const RecursionLimitException_DEPRECATED&)
|
||||
{
|
||||
reportErrorCodeTooComplex(block.location);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
struct InplaceDemoter : TypeVarOnceVisitor
|
||||
|
@ -2456,11 +2471,8 @@ std::string opToMetaTableEntry(const AstExprBinary::Op& op)
|
|||
|
||||
TypeId TypeChecker::unionOfTypes(TypeId a, TypeId b, const ScopePtr& scope, const Location& location, bool unifyFreeTypes)
|
||||
{
|
||||
if (FFlag::LuauUnionOfTypesFollow)
|
||||
{
|
||||
a = follow(a);
|
||||
b = follow(b);
|
||||
}
|
||||
a = follow(a);
|
||||
b = follow(b);
|
||||
|
||||
if (unifyFreeTypes && (get<FreeTypeVar>(a) || get<FreeTypeVar>(b)))
|
||||
{
|
||||
|
@ -3596,8 +3608,17 @@ void TypeChecker::checkArgumentList(const ScopePtr& scope, const AstExpr& funNam
|
|||
location = {state.location.begin, argLocations.back().end};
|
||||
|
||||
std::string namePath;
|
||||
if (std::optional<LValue> lValue = tryGetLValue(funName))
|
||||
namePath = toString(*lValue);
|
||||
|
||||
if (FFlag::LuauLvaluelessPath)
|
||||
{
|
||||
if (std::optional<std::string> path = getFunctionNameAsString(funName))
|
||||
namePath = *path;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (std::optional<LValue> lValue = tryGetLValue(funName))
|
||||
namePath = toString(*lValue);
|
||||
}
|
||||
|
||||
auto [minParams, optMaxParams] = getParameterExtents(&state.log, paramPack);
|
||||
state.reportError(TypeError{location,
|
||||
|
@ -3706,11 +3727,28 @@ void TypeChecker::checkArgumentList(const ScopePtr& scope, const AstExpr& funNam
|
|||
bool isVariadic = tail && Luau::isVariadic(*tail);
|
||||
|
||||
std::string namePath;
|
||||
if (std::optional<LValue> lValue = tryGetLValue(funName))
|
||||
namePath = toString(*lValue);
|
||||
|
||||
state.reportError(TypeError{
|
||||
state.location, CountMismatch{minParams, optMaxParams, paramIndex, CountMismatch::Context::Arg, isVariadic, namePath}});
|
||||
if (FFlag::LuauLvaluelessPath)
|
||||
{
|
||||
if (std::optional<std::string> path = getFunctionNameAsString(funName))
|
||||
namePath = *path;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (std::optional<LValue> lValue = tryGetLValue(funName))
|
||||
namePath = toString(*lValue);
|
||||
}
|
||||
|
||||
if (FFlag::LuauArgMismatchReportFunctionLocation)
|
||||
{
|
||||
state.reportError(TypeError{
|
||||
funName.location, CountMismatch{minParams, optMaxParams, paramIndex, CountMismatch::Context::Arg, isVariadic, namePath}});
|
||||
}
|
||||
else
|
||||
{
|
||||
state.reportError(TypeError{
|
||||
state.location, CountMismatch{minParams, optMaxParams, paramIndex, CountMismatch::Context::Arg, isVariadic, namePath}});
|
||||
}
|
||||
return;
|
||||
}
|
||||
++paramIter;
|
||||
|
@ -4647,6 +4685,19 @@ void TypeChecker::ice(const std::string& message)
|
|||
iceHandler->ice(message);
|
||||
}
|
||||
|
||||
// TODO: Inline me when LuauIceExceptionInheritanceChange is deleted.
|
||||
void TypeChecker::throwTimeLimitError()
|
||||
{
|
||||
if (FFlag::LuauIceExceptionInheritanceChange)
|
||||
{
|
||||
throw TimeLimitError(iceHandler->moduleName);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw TimeLimitError_DEPRECATED();
|
||||
}
|
||||
}
|
||||
|
||||
void TypeChecker::prepareErrorsForDisplay(ErrorVec& errVec)
|
||||
{
|
||||
// Remove errors with names that were generated by recovery from a parse error
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/TypePack.h"
|
||||
|
||||
#include "Luau/Error.h"
|
||||
#include "Luau/TxnLog.h"
|
||||
|
||||
#include <stdexcept>
|
||||
|
@ -234,7 +235,7 @@ TypePackId follow(TypePackId tp, std::function<TypePackId(TypePackId)> mapper)
|
|||
cycleTester = nullptr;
|
||||
|
||||
if (tp == cycleTester)
|
||||
throw std::runtime_error("Luau::follow detected a TypeVar cycle!!");
|
||||
throwRuntimeError("Luau::follow detected a TypeVar cycle!!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -61,7 +61,7 @@ TypeId follow(TypeId t, std::function<TypeId(TypeId)> mapper)
|
|||
{
|
||||
std::optional<TypeId> ty = utv->scope->lookup(utv->def);
|
||||
if (!ty)
|
||||
throw std::runtime_error("UseTypeVar must map to another TypeId");
|
||||
throwRuntimeError("UseTypeVar must map to another TypeId");
|
||||
return *ty;
|
||||
}
|
||||
else
|
||||
|
@ -73,7 +73,7 @@ TypeId follow(TypeId t, std::function<TypeId(TypeId)> mapper)
|
|||
{
|
||||
TypeId res = ltv->thunk();
|
||||
if (get<LazyTypeVar>(res))
|
||||
throw std::runtime_error("Lazy TypeVar cannot resolve to another Lazy TypeVar");
|
||||
throwRuntimeError("Lazy TypeVar cannot resolve to another Lazy TypeVar");
|
||||
|
||||
*asMutable(ty) = BoundTypeVar(res);
|
||||
}
|
||||
|
@ -111,7 +111,7 @@ TypeId follow(TypeId t, std::function<TypeId(TypeId)> mapper)
|
|||
cycleTester = nullptr;
|
||||
|
||||
if (t == cycleTester)
|
||||
throw std::runtime_error("Luau::follow detected a TypeVar cycle!!");
|
||||
throwRuntimeError("Luau::follow detected a TypeVar cycle!!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -946,7 +946,7 @@ void persist(TypeId ty)
|
|||
queue.push_back(mtv->table);
|
||||
queue.push_back(mtv->metatable);
|
||||
}
|
||||
else if (get<GenericTypeVar>(t) || get<AnyTypeVar>(t) || get<FreeTypeVar>(t) || get<SingletonTypeVar>(t) || get<PrimitiveTypeVar>(t))
|
||||
else if (get<GenericTypeVar>(t) || get<AnyTypeVar>(t) || get<FreeTypeVar>(t) || get<SingletonTypeVar>(t) || get<PrimitiveTypeVar>(t) || get<NegationTypeVar>(t))
|
||||
{
|
||||
}
|
||||
else
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
LUAU_FASTINT(LuauTypeInferTypePackLoopLimit);
|
||||
LUAU_FASTFLAG(LuauErrorRecoveryType);
|
||||
LUAU_FASTFLAG(LuauUnknownAndNeverType)
|
||||
LUAU_FASTFLAGVARIABLE(LuauReportTypeMismatchForTypePackUnificationFailure, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauSubtypeNormalizer, false);
|
||||
LUAU_FASTFLAGVARIABLE(LuauScalarShapeSubtyping, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauInstantiateInSubtyping, false)
|
||||
|
@ -273,7 +274,7 @@ TypeId Widen::clean(TypeId ty)
|
|||
|
||||
TypePackId Widen::clean(TypePackId)
|
||||
{
|
||||
throw std::runtime_error("Widen attempted to clean a dirty type pack?");
|
||||
throwRuntimeError("Widen attempted to clean a dirty type pack?");
|
||||
}
|
||||
|
||||
bool Widen::ignoreChildren(TypeId ty)
|
||||
|
@ -551,6 +552,12 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
|
|||
else if (log.getMutable<ClassTypeVar>(subTy))
|
||||
tryUnifyWithClass(subTy, superTy, /*reversed*/ true);
|
||||
|
||||
else if (log.get<NegationTypeVar>(superTy))
|
||||
tryUnifyTypeWithNegation(subTy, superTy);
|
||||
|
||||
else if (log.get<NegationTypeVar>(subTy))
|
||||
tryUnifyNegationWithType(subTy, superTy);
|
||||
|
||||
else
|
||||
reportError(TypeError{location, TypeMismatch{superTy, subTy}});
|
||||
|
||||
|
@ -866,13 +873,7 @@ void Unifier::tryUnifyNormalizedTypes(
|
|||
if (!get<PrimitiveTypeVar>(superNorm.numbers))
|
||||
return reportError(TypeError{location, TypeMismatch{superTy, subTy, reason, error}});
|
||||
|
||||
if (subNorm.strings && superNorm.strings)
|
||||
{
|
||||
for (auto [name, ty] : *subNorm.strings)
|
||||
if (!superNorm.strings->count(name))
|
||||
return reportError(TypeError{location, TypeMismatch{superTy, subTy, reason, error}});
|
||||
}
|
||||
else if (!subNorm.strings && superNorm.strings)
|
||||
if (!isSubtype(subNorm.strings, superNorm.strings))
|
||||
return reportError(TypeError{location, TypeMismatch{superTy, subTy, reason, error}});
|
||||
|
||||
if (get<PrimitiveTypeVar>(subNorm.threads))
|
||||
|
@ -1392,7 +1393,10 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal
|
|||
}
|
||||
else
|
||||
{
|
||||
reportError(TypeError{location, GenericError{"Failed to unify type packs"}});
|
||||
if (FFlag::LuauReportTypeMismatchForTypePackUnificationFailure)
|
||||
reportError(TypeError{location, TypePackMismatch{subTp, superTp}});
|
||||
else
|
||||
reportError(TypeError{location, GenericError{"Failed to unify type packs"}});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1441,7 +1445,10 @@ void Unifier::tryUnifyFunctions(TypeId subTy, TypeId superTy, bool isFunctionCal
|
|||
|
||||
bool shouldInstantiate = (numGenerics == 0 && subFunction->generics.size() > 0) || (numGenericPacks == 0 && subFunction->genericPacks.size() > 0);
|
||||
|
||||
if (FFlag::LuauInstantiateInSubtyping && variance == Covariant && shouldInstantiate)
|
||||
// TODO: This is unsound when the context is invariant, but the annotation burden without allowing it and without
|
||||
// read-only properties is too high for lua-apps. Read-only properties _should_ resolve their issue by allowing
|
||||
// generic methods in tables to be marked read-only.
|
||||
if (FFlag::LuauInstantiateInSubtyping && shouldInstantiate)
|
||||
{
|
||||
Instantiation instantiation{&log, types, scope->level, scope};
|
||||
|
||||
|
@ -1576,6 +1583,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
|
|||
{
|
||||
TableTypeVar* superTable = log.getMutable<TableTypeVar>(superTy);
|
||||
TableTypeVar* subTable = log.getMutable<TableTypeVar>(subTy);
|
||||
TableTypeVar* instantiatedSubTable = subTable;
|
||||
|
||||
if (!superTable || !subTable)
|
||||
ice("passed non-table types to unifyTables");
|
||||
|
@ -1593,6 +1601,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
|
|||
if (instantiated.has_value())
|
||||
{
|
||||
subTable = log.getMutable<TableTypeVar>(*instantiated);
|
||||
instantiatedSubTable = subTable;
|
||||
|
||||
if (!subTable)
|
||||
ice("instantiation made a table type into a non-table type in tryUnifyTables");
|
||||
|
@ -1696,7 +1705,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
|
|||
// txn log.
|
||||
TableTypeVar* newSuperTable = log.getMutable<TableTypeVar>(superTy);
|
||||
TableTypeVar* newSubTable = log.getMutable<TableTypeVar>(subTy);
|
||||
if (superTable != newSuperTable || subTable != newSubTable)
|
||||
if (superTable != newSuperTable || (subTable != newSubTable && subTable != instantiatedSubTable))
|
||||
{
|
||||
if (errors.empty())
|
||||
return tryUnifyTables(subTy, superTy, isIntersection);
|
||||
|
@ -1758,7 +1767,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
|
|||
// txn log.
|
||||
TableTypeVar* newSuperTable = log.getMutable<TableTypeVar>(superTy);
|
||||
TableTypeVar* newSubTable = log.getMutable<TableTypeVar>(subTy);
|
||||
if (superTable != newSuperTable || subTable != newSubTable)
|
||||
if (superTable != newSuperTable || (subTable != newSubTable && subTable != instantiatedSubTable))
|
||||
{
|
||||
if (errors.empty())
|
||||
return tryUnifyTables(subTy, superTy, isIntersection);
|
||||
|
@ -2098,6 +2107,34 @@ void Unifier::tryUnifyWithClass(TypeId subTy, TypeId superTy, bool reversed)
|
|||
return fail();
|
||||
}
|
||||
|
||||
void Unifier::tryUnifyTypeWithNegation(TypeId subTy, TypeId superTy)
|
||||
{
|
||||
const NegationTypeVar* ntv = get<NegationTypeVar>(superTy);
|
||||
if (!ntv)
|
||||
ice("tryUnifyTypeWithNegation superTy must be a negation type");
|
||||
|
||||
const NormalizedType* subNorm = normalizer->normalize(subTy);
|
||||
const NormalizedType* superNorm = normalizer->normalize(superTy);
|
||||
if (!subNorm || !superNorm)
|
||||
return reportError(TypeError{location, UnificationTooComplex{}});
|
||||
|
||||
// T </: ~U iff T <: U
|
||||
Unifier state = makeChildUnifier();
|
||||
state.tryUnifyNormalizedTypes(subTy, superTy, *subNorm, *superNorm, "");
|
||||
if (state.errors.empty())
|
||||
reportError(TypeError{location, TypeMismatch{superTy, subTy}});
|
||||
}
|
||||
|
||||
void Unifier::tryUnifyNegationWithType(TypeId subTy, TypeId superTy)
|
||||
{
|
||||
const NegationTypeVar* ntv = get<NegationTypeVar>(subTy);
|
||||
if (!ntv)
|
||||
ice("tryUnifyNegationWithType subTy must be a negation type");
|
||||
|
||||
// TODO: ~T </: U iff T <: U
|
||||
reportError(TypeError{location, TypeMismatch{superTy, subTy}});
|
||||
}
|
||||
|
||||
static void queueTypePack(std::vector<TypeId>& queue, DenseHashSet<TypePackId>& seenTypePacks, Unifier& state, TypePackId a, TypePackId anyTypePack)
|
||||
{
|
||||
while (true)
|
||||
|
|
|
@ -58,6 +58,8 @@ struct Comment
|
|||
struct ParseResult
|
||||
{
|
||||
AstStatBlock* root;
|
||||
size_t lines = 0;
|
||||
|
||||
std::vector<HotComment> hotcomments;
|
||||
std::vector<ParseError> errors;
|
||||
|
||||
|
|
|
@ -302,8 +302,8 @@ private:
|
|||
AstStatError* reportStatError(const Location& location, const AstArray<AstExpr*>& expressions, const AstArray<AstStat*>& statements,
|
||||
const char* format, ...) LUAU_PRINTF_ATTR(5, 6);
|
||||
AstExprError* reportExprError(const Location& location, const AstArray<AstExpr*>& expressions, const char* format, ...) LUAU_PRINTF_ATTR(4, 5);
|
||||
AstTypeError* reportTypeAnnotationError(const Location& location, const AstArray<AstType*>& types, bool isMissing, const char* format, ...)
|
||||
LUAU_PRINTF_ATTR(5, 6);
|
||||
AstTypeError* reportTypeAnnotationError(const Location& location, const AstArray<AstType*>& types, const char* format, ...)
|
||||
LUAU_PRINTF_ATTR(4, 5);
|
||||
// `parseErrorLocation` is associated with the parser error
|
||||
// `astErrorLocation` is associated with the AstTypeError created
|
||||
// It can be useful to have different error locations so that the parse error can include the next lexeme, while the AstTypeError can precisely
|
||||
|
|
|
@ -23,7 +23,6 @@ LUAU_FASTFLAGVARIABLE(LuauErrorDoubleHexPrefix, false)
|
|||
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuaReportParseIntegerIssues, false)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauInterpolatedStringBaseSupport, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTypeAnnotationLocationChange, false)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauCommaParenWarnings, false)
|
||||
|
||||
|
@ -164,15 +163,16 @@ ParseResult Parser::parse(const char* buffer, size_t bufferSize, AstNameTable& n
|
|||
try
|
||||
{
|
||||
AstStatBlock* root = p.parseChunk();
|
||||
size_t lines = p.lexer.current().location.end.line + (bufferSize > 0 && buffer[bufferSize - 1] != '\n');
|
||||
|
||||
return ParseResult{root, std::move(p.hotcomments), std::move(p.parseErrors), std::move(p.commentLocations)};
|
||||
return ParseResult{root, lines, std::move(p.hotcomments), std::move(p.parseErrors), std::move(p.commentLocations)};
|
||||
}
|
||||
catch (ParseError& err)
|
||||
{
|
||||
// when catching a fatal error, append it to the list of non-fatal errors and return
|
||||
p.parseErrors.push_back(err);
|
||||
|
||||
return ParseResult{nullptr, {}, p.parseErrors};
|
||||
return ParseResult{nullptr, 0, {}, p.parseErrors};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -811,9 +811,8 @@ AstDeclaredClassProp Parser::parseDeclaredClassMethod()
|
|||
|
||||
if (args.size() == 0 || args[0].name.name != "self" || args[0].annotation != nullptr)
|
||||
{
|
||||
return AstDeclaredClassProp{fnName.name,
|
||||
reportTypeAnnotationError(Location(start, end), {}, /*isMissing*/ false, "'self' must be present as the unannotated first parameter"),
|
||||
true};
|
||||
return AstDeclaredClassProp{
|
||||
fnName.name, reportTypeAnnotationError(Location(start, end), {}, "'self' must be present as the unannotated first parameter"), true};
|
||||
}
|
||||
|
||||
// Skip the first index.
|
||||
|
@ -824,8 +823,7 @@ AstDeclaredClassProp Parser::parseDeclaredClassMethod()
|
|||
if (args[i].annotation)
|
||||
vars.push_back(args[i].annotation);
|
||||
else
|
||||
vars.push_back(reportTypeAnnotationError(
|
||||
Location(start, end), {}, /*isMissing*/ false, "All declaration parameters aside from 'self' must be annotated"));
|
||||
vars.push_back(reportTypeAnnotationError(Location(start, end), {}, "All declaration parameters aside from 'self' must be annotated"));
|
||||
}
|
||||
|
||||
if (vararg && !varargAnnotation)
|
||||
|
@ -1537,7 +1535,7 @@ AstType* Parser::parseTypeAnnotation(TempVector<AstType*>& parts, const Location
|
|||
|
||||
if (isUnion && isIntersection)
|
||||
{
|
||||
return reportTypeAnnotationError(Location(begin, parts.back()->location), copy(parts), /*isMissing*/ false,
|
||||
return reportTypeAnnotationError(Location(begin, parts.back()->location), copy(parts),
|
||||
"Mixing union and intersection types is not allowed; consider wrapping in parentheses.");
|
||||
}
|
||||
|
||||
|
@ -1623,18 +1621,18 @@ AstTypeOrPack Parser::parseSimpleTypeAnnotation(bool allowPack)
|
|||
return {allocator.alloc<AstTypeSingletonString>(start, svalue)};
|
||||
}
|
||||
else
|
||||
return {reportTypeAnnotationError(start, {}, /*isMissing*/ false, "String literal contains malformed escape sequence")};
|
||||
return {reportTypeAnnotationError(start, {}, "String literal contains malformed escape sequence")};
|
||||
}
|
||||
else if (lexer.current().type == Lexeme::InterpStringBegin || lexer.current().type == Lexeme::InterpStringSimple)
|
||||
{
|
||||
parseInterpString();
|
||||
|
||||
return {reportTypeAnnotationError(start, {}, /*isMissing*/ false, "Interpolated string literals cannot be used as types")};
|
||||
return {reportTypeAnnotationError(start, {}, "Interpolated string literals cannot be used as types")};
|
||||
}
|
||||
else if (lexer.current().type == Lexeme::BrokenString)
|
||||
{
|
||||
nextLexeme();
|
||||
return {reportTypeAnnotationError(start, {}, /*isMissing*/ false, "Malformed string")};
|
||||
return {reportTypeAnnotationError(start, {}, "Malformed string")};
|
||||
}
|
||||
else if (lexer.current().type == Lexeme::Name)
|
||||
{
|
||||
|
@ -1693,33 +1691,20 @@ AstTypeOrPack Parser::parseSimpleTypeAnnotation(bool allowPack)
|
|||
{
|
||||
nextLexeme();
|
||||
|
||||
return {reportTypeAnnotationError(start, {}, /*isMissing*/ false,
|
||||
return {reportTypeAnnotationError(start, {},
|
||||
"Using 'function' as a type annotation is not supported, consider replacing with a function type annotation e.g. '(...any) -> "
|
||||
"...any'"),
|
||||
{}};
|
||||
}
|
||||
else
|
||||
{
|
||||
if (FFlag::LuauTypeAnnotationLocationChange)
|
||||
{
|
||||
// For a missing type annotation, capture 'space' between last token and the next one
|
||||
Location astErrorlocation(lexer.previousLocation().end, start.begin);
|
||||
// The parse error includes the next lexeme to make it easier to display where the error is (e.g. in an IDE or a CLI error message).
|
||||
// Including the current lexeme also makes the parse error consistent with other parse errors returned by Luau.
|
||||
Location parseErrorLocation(lexer.previousLocation().end, start.end);
|
||||
return {
|
||||
reportMissingTypeAnnotationError(parseErrorLocation, astErrorlocation, "Expected type, got %s", lexer.current().toString().c_str()),
|
||||
{}};
|
||||
}
|
||||
else
|
||||
{
|
||||
Location location = lexer.current().location;
|
||||
|
||||
// For a missing type annotation, capture 'space' between last token and the next one
|
||||
location = Location(lexer.previousLocation().end, lexer.current().location.begin);
|
||||
|
||||
return {reportTypeAnnotationError(location, {}, /*isMissing*/ true, "Expected type, got %s", lexer.current().toString().c_str()), {}};
|
||||
}
|
||||
// For a missing type annotation, capture 'space' between last token and the next one
|
||||
Location astErrorlocation(lexer.previousLocation().end, start.begin);
|
||||
// The parse error includes the next lexeme to make it easier to display where the error is (e.g. in an IDE or a CLI error message).
|
||||
// Including the current lexeme also makes the parse error consistent with other parse errors returned by Luau.
|
||||
Location parseErrorLocation(lexer.previousLocation().end, start.end);
|
||||
return {
|
||||
reportMissingTypeAnnotationError(parseErrorLocation, astErrorlocation, "Expected type, got %s", lexer.current().toString().c_str()), {}};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3033,27 +3018,18 @@ AstExprError* Parser::reportExprError(const Location& location, const AstArray<A
|
|||
return allocator.alloc<AstExprError>(location, expressions, unsigned(parseErrors.size() - 1));
|
||||
}
|
||||
|
||||
AstTypeError* Parser::reportTypeAnnotationError(const Location& location, const AstArray<AstType*>& types, bool isMissing, const char* format, ...)
|
||||
AstTypeError* Parser::reportTypeAnnotationError(const Location& location, const AstArray<AstType*>& types, const char* format, ...)
|
||||
{
|
||||
if (FFlag::LuauTypeAnnotationLocationChange)
|
||||
{
|
||||
// Missing type annotations should be using `reportMissingTypeAnnotationError` when LuauTypeAnnotationLocationChange is enabled
|
||||
// Note: `isMissing` can be removed once FFlag::LuauTypeAnnotationLocationChange is removed since it will always be true.
|
||||
LUAU_ASSERT(!isMissing);
|
||||
}
|
||||
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
report(location, format, args);
|
||||
va_end(args);
|
||||
|
||||
return allocator.alloc<AstTypeError>(location, types, isMissing, unsigned(parseErrors.size() - 1));
|
||||
return allocator.alloc<AstTypeError>(location, types, false, unsigned(parseErrors.size() - 1));
|
||||
}
|
||||
|
||||
AstTypeError* Parser::reportMissingTypeAnnotationError(const Location& parseErrorLocation, const Location& astErrorLocation, const char* format, ...)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauTypeAnnotationLocationChange);
|
||||
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
report(parseErrorLocation, format, args);
|
||||
|
|
19
CLI/Repl.cpp
19
CLI/Repl.cpp
|
@ -16,7 +16,6 @@
|
|||
|
||||
#include "isocline.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
|
||||
#ifdef _WIN32
|
||||
|
@ -688,11 +687,11 @@ static std::string getCodegenAssembly(const char* name, const std::string& bytec
|
|||
return "";
|
||||
}
|
||||
|
||||
static void annotateInstruction(void* context, std::string& text, int fid, int instid)
|
||||
static void annotateInstruction(void* context, std::string& text, int fid, int instpos)
|
||||
{
|
||||
Luau::BytecodeBuilder& bcb = *(Luau::BytecodeBuilder*)context;
|
||||
|
||||
bcb.annotateInstruction(text, fid, instid);
|
||||
bcb.annotateInstruction(text, fid, instpos);
|
||||
}
|
||||
|
||||
struct CompileStats
|
||||
|
@ -711,7 +710,8 @@ static bool compileFile(const char* name, CompileFormat format, CompileStats& st
|
|||
return false;
|
||||
}
|
||||
|
||||
stats.lines += std::count(source->begin(), source->end(), '\n');
|
||||
// NOTE: Normally, you should use Luau::compile or luau_compile (see lua_require as an example)
|
||||
// This function is much more complicated because it supports many output human-readable formats through internal interfaces
|
||||
|
||||
try
|
||||
{
|
||||
|
@ -736,7 +736,16 @@ static bool compileFile(const char* name, CompileFormat format, CompileStats& st
|
|||
bcb.setDumpSource(*source);
|
||||
}
|
||||
|
||||
Luau::compileOrThrow(bcb, *source, copts());
|
||||
Luau::Allocator allocator;
|
||||
Luau::AstNameTable names(allocator);
|
||||
Luau::ParseResult result = Luau::Parser::parse(source->c_str(), source->size(), names, allocator);
|
||||
|
||||
if (!result.errors.empty())
|
||||
throw Luau::ParseErrors(result.errors);
|
||||
|
||||
stats.lines += result.lines;
|
||||
|
||||
Luau::compileOrThrow(bcb, result, names, copts());
|
||||
stats.bytecode += bcb.getBytecode().size();
|
||||
|
||||
switch (format)
|
||||
|
|
|
@ -143,6 +143,11 @@ if (MSVC AND MSVC_VERSION GREATER_EQUAL 1924)
|
|||
set_source_files_properties(VM/src/lvmexecute.cpp PROPERTIES COMPILE_FLAGS /d2ssa-pre-)
|
||||
endif()
|
||||
|
||||
if (NOT MSVC)
|
||||
# disable support for math_errno which allows compilers to lower sqrt() into a single CPU instruction
|
||||
target_compile_options(Luau.VM PRIVATE -fno-math-errno)
|
||||
endif()
|
||||
|
||||
if(MSVC AND LUAU_BUILD_CLI)
|
||||
# the default stack size that MSVC linker uses is 1 MB; we need more stack space in Debug because stack frames are larger
|
||||
set_target_properties(Luau.Analyze.CLI PROPERTIES LINK_FLAGS_DEBUG /STACK:2097152)
|
||||
|
|
|
@ -17,7 +17,7 @@ void create(lua_State* L);
|
|||
// Builds target function and all inner functions
|
||||
void compile(lua_State* L, int idx);
|
||||
|
||||
using annotatorFn = void (*)(void* context, std::string& result, int fid, int instid);
|
||||
using annotatorFn = void (*)(void* context, std::string& result, int fid, int instpos);
|
||||
|
||||
struct AssemblyOptions
|
||||
{
|
||||
|
|
|
@ -34,7 +34,350 @@ namespace CodeGen
|
|||
|
||||
constexpr uint32_t kFunctionAlignment = 32;
|
||||
|
||||
static NativeProto* assembleFunction(AssemblyBuilderX64& build, NativeState& data, Proto* proto, AssemblyOptions options)
|
||||
struct InstructionOutline
|
||||
{
|
||||
int pcpos;
|
||||
int length;
|
||||
};
|
||||
|
||||
static void assembleHelpers(AssemblyBuilderX64& build, ModuleHelpers& helpers)
|
||||
{
|
||||
if (build.logText)
|
||||
build.logAppend("; exitContinueVm\n");
|
||||
helpers.exitContinueVm = build.setLabel();
|
||||
emitExit(build, /* continueInVm */ true);
|
||||
|
||||
if (build.logText)
|
||||
build.logAppend("; exitNoContinueVm\n");
|
||||
helpers.exitNoContinueVm = build.setLabel();
|
||||
emitExit(build, /* continueInVm */ false);
|
||||
}
|
||||
|
||||
static int emitInst(
|
||||
AssemblyBuilderX64& build, NativeState& data, ModuleHelpers& helpers, Proto* proto, LuauOpcode op, const Instruction* pc, int i, Label* labelarr, Label& fallback)
|
||||
{
|
||||
int skip = 0;
|
||||
|
||||
switch (op)
|
||||
{
|
||||
case LOP_NOP:
|
||||
break;
|
||||
case LOP_LOADNIL:
|
||||
emitInstLoadNil(build, pc);
|
||||
break;
|
||||
case LOP_LOADB:
|
||||
emitInstLoadB(build, pc, i, labelarr);
|
||||
break;
|
||||
case LOP_LOADN:
|
||||
emitInstLoadN(build, pc);
|
||||
break;
|
||||
case LOP_LOADK:
|
||||
emitInstLoadK(build, pc);
|
||||
break;
|
||||
case LOP_LOADKX:
|
||||
emitInstLoadKX(build, pc);
|
||||
break;
|
||||
case LOP_MOVE:
|
||||
emitInstMove(build, pc);
|
||||
break;
|
||||
case LOP_GETGLOBAL:
|
||||
emitInstGetGlobal(build, pc, i, fallback);
|
||||
break;
|
||||
case LOP_SETGLOBAL:
|
||||
emitInstSetGlobal(build, pc, i, labelarr, fallback);
|
||||
break;
|
||||
case LOP_RETURN:
|
||||
emitInstReturn(build, helpers, pc, i, labelarr);
|
||||
break;
|
||||
case LOP_GETTABLE:
|
||||
emitInstGetTable(build, pc, i, fallback);
|
||||
break;
|
||||
case LOP_SETTABLE:
|
||||
emitInstSetTable(build, pc, i, labelarr, fallback);
|
||||
break;
|
||||
case LOP_GETTABLEKS:
|
||||
emitInstGetTableKS(build, pc, i, fallback);
|
||||
break;
|
||||
case LOP_SETTABLEKS:
|
||||
emitInstSetTableKS(build, pc, i, labelarr, fallback);
|
||||
break;
|
||||
case LOP_GETTABLEN:
|
||||
emitInstGetTableN(build, pc, i, fallback);
|
||||
break;
|
||||
case LOP_SETTABLEN:
|
||||
emitInstSetTableN(build, pc, i, labelarr, fallback);
|
||||
break;
|
||||
case LOP_JUMP:
|
||||
emitInstJump(build, pc, i, labelarr);
|
||||
break;
|
||||
case LOP_JUMPBACK:
|
||||
emitInstJumpBack(build, pc, i, labelarr);
|
||||
break;
|
||||
case LOP_JUMPIF:
|
||||
emitInstJumpIf(build, pc, i, labelarr, /* not_ */ false);
|
||||
break;
|
||||
case LOP_JUMPIFNOT:
|
||||
emitInstJumpIf(build, pc, i, labelarr, /* not_ */ true);
|
||||
break;
|
||||
case LOP_JUMPIFEQ:
|
||||
emitInstJumpIfEq(build, pc, i, labelarr, /* not_ */ false, fallback);
|
||||
break;
|
||||
case LOP_JUMPIFLE:
|
||||
emitInstJumpIfCond(build, pc, i, labelarr, Condition::LessEqual, fallback);
|
||||
break;
|
||||
case LOP_JUMPIFLT:
|
||||
emitInstJumpIfCond(build, pc, i, labelarr, Condition::Less, fallback);
|
||||
break;
|
||||
case LOP_JUMPIFNOTEQ:
|
||||
emitInstJumpIfEq(build, pc, i, labelarr, /* not_ */ true, fallback);
|
||||
break;
|
||||
case LOP_JUMPIFNOTLE:
|
||||
emitInstJumpIfCond(build, pc, i, labelarr, Condition::NotLessEqual, fallback);
|
||||
break;
|
||||
case LOP_JUMPIFNOTLT:
|
||||
emitInstJumpIfCond(build, pc, i, labelarr, Condition::NotLess, fallback);
|
||||
break;
|
||||
case LOP_JUMPX:
|
||||
emitInstJumpX(build, pc, i, labelarr);
|
||||
break;
|
||||
case LOP_JUMPXEQKNIL:
|
||||
emitInstJumpxEqNil(build, pc, i, labelarr);
|
||||
break;
|
||||
case LOP_JUMPXEQKB:
|
||||
emitInstJumpxEqB(build, pc, i, labelarr);
|
||||
break;
|
||||
case LOP_JUMPXEQKN:
|
||||
emitInstJumpxEqN(build, pc, proto->k, i, labelarr);
|
||||
break;
|
||||
case LOP_JUMPXEQKS:
|
||||
emitInstJumpxEqS(build, pc, i, labelarr);
|
||||
break;
|
||||
case LOP_ADD:
|
||||
emitInstBinary(build, pc, i, TM_ADD, fallback);
|
||||
break;
|
||||
case LOP_SUB:
|
||||
emitInstBinary(build, pc, i, TM_SUB, fallback);
|
||||
break;
|
||||
case LOP_MUL:
|
||||
emitInstBinary(build, pc, i, TM_MUL, fallback);
|
||||
break;
|
||||
case LOP_DIV:
|
||||
emitInstBinary(build, pc, i, TM_DIV, fallback);
|
||||
break;
|
||||
case LOP_MOD:
|
||||
emitInstBinary(build, pc, i, TM_MOD, fallback);
|
||||
break;
|
||||
case LOP_POW:
|
||||
emitInstBinary(build, pc, i, TM_POW, fallback);
|
||||
break;
|
||||
case LOP_ADDK:
|
||||
emitInstBinaryK(build, pc, i, TM_ADD, fallback);
|
||||
break;
|
||||
case LOP_SUBK:
|
||||
emitInstBinaryK(build, pc, i, TM_SUB, fallback);
|
||||
break;
|
||||
case LOP_MULK:
|
||||
emitInstBinaryK(build, pc, i, TM_MUL, fallback);
|
||||
break;
|
||||
case LOP_DIVK:
|
||||
emitInstBinaryK(build, pc, i, TM_DIV, fallback);
|
||||
break;
|
||||
case LOP_MODK:
|
||||
emitInstBinaryK(build, pc, i, TM_MOD, fallback);
|
||||
break;
|
||||
case LOP_POWK:
|
||||
emitInstPowK(build, pc, proto->k, i, fallback);
|
||||
break;
|
||||
case LOP_NOT:
|
||||
emitInstNot(build, pc);
|
||||
break;
|
||||
case LOP_MINUS:
|
||||
emitInstMinus(build, pc, i, fallback);
|
||||
break;
|
||||
case LOP_LENGTH:
|
||||
emitInstLength(build, pc, i, fallback);
|
||||
break;
|
||||
case LOP_NEWTABLE:
|
||||
emitInstNewTable(build, pc, i, labelarr);
|
||||
break;
|
||||
case LOP_DUPTABLE:
|
||||
emitInstDupTable(build, pc, i, labelarr);
|
||||
break;
|
||||
case LOP_SETLIST:
|
||||
emitInstSetList(build, pc, i, labelarr);
|
||||
break;
|
||||
case LOP_GETUPVAL:
|
||||
emitInstGetUpval(build, pc, i);
|
||||
break;
|
||||
case LOP_SETUPVAL:
|
||||
emitInstSetUpval(build, pc, i, labelarr);
|
||||
break;
|
||||
case LOP_CLOSEUPVALS:
|
||||
emitInstCloseUpvals(build, pc, i, labelarr);
|
||||
break;
|
||||
case LOP_FASTCALL:
|
||||
skip = emitInstFastCall(build, pc, i, labelarr);
|
||||
break;
|
||||
case LOP_FASTCALL1:
|
||||
skip = emitInstFastCall1(build, pc, i, labelarr);
|
||||
break;
|
||||
case LOP_FASTCALL2:
|
||||
skip = emitInstFastCall2(build, pc, i, labelarr);
|
||||
break;
|
||||
case LOP_FASTCALL2K:
|
||||
skip = emitInstFastCall2K(build, pc, i, labelarr);
|
||||
break;
|
||||
case LOP_FORNPREP:
|
||||
emitInstForNPrep(build, pc, i, labelarr);
|
||||
break;
|
||||
case LOP_FORNLOOP:
|
||||
emitInstForNLoop(build, pc, i, labelarr);
|
||||
break;
|
||||
case LOP_FORGLOOP:
|
||||
emitinstForGLoop(build, pc, i, labelarr, fallback);
|
||||
break;
|
||||
case LOP_FORGPREP_NEXT:
|
||||
emitInstForGPrepNext(build, pc, i, labelarr, fallback);
|
||||
break;
|
||||
case LOP_FORGPREP_INEXT:
|
||||
emitInstForGPrepInext(build, pc, i, labelarr, fallback);
|
||||
break;
|
||||
case LOP_AND:
|
||||
emitInstAnd(build, pc);
|
||||
break;
|
||||
case LOP_ANDK:
|
||||
emitInstAndK(build, pc);
|
||||
break;
|
||||
case LOP_OR:
|
||||
emitInstOr(build, pc);
|
||||
break;
|
||||
case LOP_ORK:
|
||||
emitInstOrK(build, pc);
|
||||
break;
|
||||
case LOP_GETIMPORT:
|
||||
emitInstGetImport(build, pc, fallback);
|
||||
break;
|
||||
case LOP_CONCAT:
|
||||
emitInstConcat(build, pc, i, labelarr);
|
||||
break;
|
||||
default:
|
||||
emitFallback(build, data, op, i);
|
||||
break;
|
||||
}
|
||||
|
||||
return skip;
|
||||
}
|
||||
|
||||
static void emitInstFallback(AssemblyBuilderX64& build, NativeState& data, LuauOpcode op, const Instruction* pc, int i, Label* labelarr)
|
||||
{
|
||||
switch (op)
|
||||
{
|
||||
case LOP_GETIMPORT:
|
||||
emitInstGetImportFallback(build, pc, i);
|
||||
break;
|
||||
case LOP_GETTABLE:
|
||||
emitInstGetTableFallback(build, pc, i);
|
||||
break;
|
||||
case LOP_SETTABLE:
|
||||
emitInstSetTableFallback(build, pc, i);
|
||||
break;
|
||||
case LOP_GETTABLEN:
|
||||
emitInstGetTableNFallback(build, pc, i);
|
||||
break;
|
||||
case LOP_SETTABLEN:
|
||||
emitInstSetTableNFallback(build, pc, i);
|
||||
break;
|
||||
case LOP_JUMPIFEQ:
|
||||
emitInstJumpIfEqFallback(build, pc, i, labelarr, /* not_ */ false);
|
||||
break;
|
||||
case LOP_JUMPIFLE:
|
||||
emitInstJumpIfCondFallback(build, pc, i, labelarr, Condition::LessEqual);
|
||||
break;
|
||||
case LOP_JUMPIFLT:
|
||||
emitInstJumpIfCondFallback(build, pc, i, labelarr, Condition::Less);
|
||||
break;
|
||||
case LOP_JUMPIFNOTEQ:
|
||||
emitInstJumpIfEqFallback(build, pc, i, labelarr, /* not_ */ true);
|
||||
break;
|
||||
case LOP_JUMPIFNOTLE:
|
||||
emitInstJumpIfCondFallback(build, pc, i, labelarr, Condition::NotLessEqual);
|
||||
break;
|
||||
case LOP_JUMPIFNOTLT:
|
||||
emitInstJumpIfCondFallback(build, pc, i, labelarr, Condition::NotLess);
|
||||
break;
|
||||
case LOP_ADD:
|
||||
emitInstBinaryFallback(build, pc, i, TM_ADD);
|
||||
break;
|
||||
case LOP_SUB:
|
||||
emitInstBinaryFallback(build, pc, i, TM_SUB);
|
||||
break;
|
||||
case LOP_MUL:
|
||||
emitInstBinaryFallback(build, pc, i, TM_MUL);
|
||||
break;
|
||||
case LOP_DIV:
|
||||
emitInstBinaryFallback(build, pc, i, TM_DIV);
|
||||
break;
|
||||
case LOP_MOD:
|
||||
emitInstBinaryFallback(build, pc, i, TM_MOD);
|
||||
break;
|
||||
case LOP_POW:
|
||||
emitInstBinaryFallback(build, pc, i, TM_POW);
|
||||
break;
|
||||
case LOP_ADDK:
|
||||
emitInstBinaryKFallback(build, pc, i, TM_ADD);
|
||||
break;
|
||||
case LOP_SUBK:
|
||||
emitInstBinaryKFallback(build, pc, i, TM_SUB);
|
||||
break;
|
||||
case LOP_MULK:
|
||||
emitInstBinaryKFallback(build, pc, i, TM_MUL);
|
||||
break;
|
||||
case LOP_DIVK:
|
||||
emitInstBinaryKFallback(build, pc, i, TM_DIV);
|
||||
break;
|
||||
case LOP_MODK:
|
||||
emitInstBinaryKFallback(build, pc, i, TM_MOD);
|
||||
break;
|
||||
case LOP_POWK:
|
||||
emitInstBinaryKFallback(build, pc, i, TM_POW);
|
||||
break;
|
||||
case LOP_MINUS:
|
||||
emitInstMinusFallback(build, pc, i);
|
||||
break;
|
||||
case LOP_LENGTH:
|
||||
emitInstLengthFallback(build, pc, i);
|
||||
break;
|
||||
case LOP_FORGLOOP:
|
||||
emitinstForGLoopFallback(build, pc, i, labelarr);
|
||||
break;
|
||||
case LOP_FORGPREP_NEXT:
|
||||
case LOP_FORGPREP_INEXT:
|
||||
emitInstForGPrepXnextFallback(build, pc, i, labelarr);
|
||||
break;
|
||||
case LOP_GETGLOBAL:
|
||||
// TODO: luaV_gettable + cachedslot update instead of full fallback
|
||||
emitFallback(build, data, op, i);
|
||||
break;
|
||||
case LOP_SETGLOBAL:
|
||||
// TODO: luaV_settable + cachedslot update instead of full fallback
|
||||
emitFallback(build, data, op, i);
|
||||
break;
|
||||
case LOP_GETTABLEKS:
|
||||
// Full fallback required for LOP_GETTABLEKS because 'luaV_gettable' doesn't handle builtin vector field access
|
||||
// It is also required to perform cached slot update
|
||||
// TODO: extra fast-paths could be lowered before the full fallback
|
||||
emitFallback(build, data, op, i);
|
||||
break;
|
||||
case LOP_SETTABLEKS:
|
||||
// TODO: luaV_settable + cachedslot update instead of full fallback
|
||||
emitFallback(build, data, op, i);
|
||||
break;
|
||||
default:
|
||||
LUAU_ASSERT(!"Expected fallback for instruction");
|
||||
}
|
||||
}
|
||||
|
||||
static NativeProto* assembleFunction(AssemblyBuilderX64& build, NativeState& data, ModuleHelpers& helpers, Proto* proto, AssemblyOptions options)
|
||||
{
|
||||
NativeProto* result = new NativeProto();
|
||||
|
||||
|
@ -59,222 +402,65 @@ static NativeProto* assembleFunction(AssemblyBuilderX64& build, NativeState& dat
|
|||
std::vector<Label> instFallbacks;
|
||||
instFallbacks.resize(proto->sizecode);
|
||||
|
||||
std::vector<InstructionOutline> instOutlines;
|
||||
instOutlines.reserve(64);
|
||||
|
||||
build.align(kFunctionAlignment, AlignmentDataX64::Ud2);
|
||||
|
||||
Label start = build.setLabel();
|
||||
|
||||
for (int i = 0, instid = 0; i < proto->sizecode; ++instid)
|
||||
for (int i = 0; i < proto->sizecode;)
|
||||
{
|
||||
const Instruction* pc = &proto->code[i];
|
||||
LuauOpcode op = LuauOpcode(LUAU_INSN_OP(*pc));
|
||||
|
||||
int nexti = i + getOpLength(op);
|
||||
LUAU_ASSERT(nexti <= proto->sizecode);
|
||||
|
||||
build.setLabel(instLabels[i]);
|
||||
|
||||
if (options.annotator)
|
||||
options.annotator(options.annotatorContext, build.text, proto->bytecodeid, instid);
|
||||
options.annotator(options.annotatorContext, build.text, proto->bytecodeid, i);
|
||||
|
||||
switch (op)
|
||||
{
|
||||
case LOP_NOP:
|
||||
break;
|
||||
case LOP_LOADNIL:
|
||||
emitInstLoadNil(build, pc);
|
||||
break;
|
||||
case LOP_LOADB:
|
||||
emitInstLoadB(build, pc, i, instLabels.data());
|
||||
break;
|
||||
case LOP_LOADN:
|
||||
emitInstLoadN(build, pc);
|
||||
break;
|
||||
case LOP_LOADK:
|
||||
emitInstLoadK(build, pc);
|
||||
break;
|
||||
case LOP_LOADKX:
|
||||
emitInstLoadKX(build, pc);
|
||||
break;
|
||||
case LOP_MOVE:
|
||||
emitInstMove(build, pc);
|
||||
break;
|
||||
case LOP_GETGLOBAL:
|
||||
emitInstGetGlobal(build, pc, i, instFallbacks[i]);
|
||||
break;
|
||||
case LOP_SETGLOBAL:
|
||||
emitInstSetGlobal(build, pc, i, instLabels.data(), instFallbacks[i]);
|
||||
break;
|
||||
case LOP_GETTABLE:
|
||||
emitInstGetTable(build, pc, i, instFallbacks[i]);
|
||||
break;
|
||||
case LOP_SETTABLE:
|
||||
emitInstSetTable(build, pc, i, instLabels.data(), instFallbacks[i]);
|
||||
break;
|
||||
case LOP_GETTABLEKS:
|
||||
emitInstGetTableKS(build, pc, i, instFallbacks[i]);
|
||||
break;
|
||||
case LOP_SETTABLEKS:
|
||||
emitInstSetTableKS(build, pc, i, instLabels.data(), instFallbacks[i]);
|
||||
break;
|
||||
case LOP_GETTABLEN:
|
||||
emitInstGetTableN(build, pc, i, instFallbacks[i]);
|
||||
break;
|
||||
case LOP_SETTABLEN:
|
||||
emitInstSetTableN(build, pc, i, instLabels.data(), instFallbacks[i]);
|
||||
break;
|
||||
case LOP_JUMP:
|
||||
emitInstJump(build, pc, i, instLabels.data());
|
||||
break;
|
||||
case LOP_JUMPBACK:
|
||||
emitInstJumpBack(build, pc, i, instLabels.data());
|
||||
break;
|
||||
case LOP_JUMPIF:
|
||||
emitInstJumpIf(build, pc, i, instLabels.data(), /* not_ */ false);
|
||||
break;
|
||||
case LOP_JUMPIFNOT:
|
||||
emitInstJumpIf(build, pc, i, instLabels.data(), /* not_ */ true);
|
||||
break;
|
||||
case LOP_JUMPIFEQ:
|
||||
emitInstJumpIfEq(build, pc, i, instLabels.data(), /* not_ */ false, instFallbacks[i]);
|
||||
break;
|
||||
case LOP_JUMPIFLE:
|
||||
emitInstJumpIfCond(build, pc, i, instLabels.data(), Condition::LessEqual, instFallbacks[i]);
|
||||
break;
|
||||
case LOP_JUMPIFLT:
|
||||
emitInstJumpIfCond(build, pc, i, instLabels.data(), Condition::Less, instFallbacks[i]);
|
||||
break;
|
||||
case LOP_JUMPIFNOTEQ:
|
||||
emitInstJumpIfEq(build, pc, i, instLabels.data(), /* not_ */ true, instFallbacks[i]);
|
||||
break;
|
||||
case LOP_JUMPIFNOTLE:
|
||||
emitInstJumpIfCond(build, pc, i, instLabels.data(), Condition::NotLessEqual, instFallbacks[i]);
|
||||
break;
|
||||
case LOP_JUMPIFNOTLT:
|
||||
emitInstJumpIfCond(build, pc, i, instLabels.data(), Condition::NotLess, instFallbacks[i]);
|
||||
break;
|
||||
case LOP_JUMPX:
|
||||
emitInstJumpX(build, pc, i, instLabels.data());
|
||||
break;
|
||||
case LOP_JUMPXEQKNIL:
|
||||
emitInstJumpxEqNil(build, pc, i, instLabels.data());
|
||||
break;
|
||||
case LOP_JUMPXEQKB:
|
||||
emitInstJumpxEqB(build, pc, i, instLabels.data());
|
||||
break;
|
||||
case LOP_JUMPXEQKN:
|
||||
emitInstJumpxEqN(build, pc, proto->k, i, instLabels.data());
|
||||
break;
|
||||
case LOP_JUMPXEQKS:
|
||||
emitInstJumpxEqS(build, pc, i, instLabels.data());
|
||||
break;
|
||||
case LOP_ADD:
|
||||
emitInstBinary(build, pc, i, TM_ADD, instFallbacks[i]);
|
||||
break;
|
||||
case LOP_SUB:
|
||||
emitInstBinary(build, pc, i, TM_SUB, instFallbacks[i]);
|
||||
break;
|
||||
case LOP_MUL:
|
||||
emitInstBinary(build, pc, i, TM_MUL, instFallbacks[i]);
|
||||
break;
|
||||
case LOP_DIV:
|
||||
emitInstBinary(build, pc, i, TM_DIV, instFallbacks[i]);
|
||||
break;
|
||||
case LOP_MOD:
|
||||
emitInstBinary(build, pc, i, TM_MOD, instFallbacks[i]);
|
||||
break;
|
||||
case LOP_POW:
|
||||
emitInstBinary(build, pc, i, TM_POW, instFallbacks[i]);
|
||||
break;
|
||||
case LOP_ADDK:
|
||||
emitInstBinaryK(build, pc, i, TM_ADD, instFallbacks[i]);
|
||||
break;
|
||||
case LOP_SUBK:
|
||||
emitInstBinaryK(build, pc, i, TM_SUB, instFallbacks[i]);
|
||||
break;
|
||||
case LOP_MULK:
|
||||
emitInstBinaryK(build, pc, i, TM_MUL, instFallbacks[i]);
|
||||
break;
|
||||
case LOP_DIVK:
|
||||
emitInstBinaryK(build, pc, i, TM_DIV, instFallbacks[i]);
|
||||
break;
|
||||
case LOP_MODK:
|
||||
emitInstBinaryK(build, pc, i, TM_MOD, instFallbacks[i]);
|
||||
break;
|
||||
case LOP_POWK:
|
||||
emitInstPowK(build, pc, proto->k, i, instFallbacks[i]);
|
||||
break;
|
||||
case LOP_NOT:
|
||||
emitInstNot(build, pc);
|
||||
break;
|
||||
case LOP_MINUS:
|
||||
emitInstMinus(build, pc, i, instFallbacks[i]);
|
||||
break;
|
||||
case LOP_LENGTH:
|
||||
emitInstLength(build, pc, i, instFallbacks[i]);
|
||||
break;
|
||||
case LOP_NEWTABLE:
|
||||
emitInstNewTable(build, pc, i, instLabels.data());
|
||||
break;
|
||||
case LOP_DUPTABLE:
|
||||
emitInstDupTable(build, pc, i, instLabels.data());
|
||||
break;
|
||||
case LOP_SETLIST:
|
||||
emitInstSetList(build, pc, i, instLabels.data());
|
||||
break;
|
||||
case LOP_GETUPVAL:
|
||||
emitInstGetUpval(build, pc, i);
|
||||
break;
|
||||
case LOP_SETUPVAL:
|
||||
emitInstSetUpval(build, pc, i, instLabels.data());
|
||||
break;
|
||||
case LOP_CLOSEUPVALS:
|
||||
emitInstCloseUpvals(build, pc, i, instLabels.data());
|
||||
break;
|
||||
case LOP_FASTCALL:
|
||||
emitInstFastCall(build, pc, i, instLabels.data());
|
||||
break;
|
||||
case LOP_FASTCALL1:
|
||||
emitInstFastCall1(build, pc, i, instLabels.data());
|
||||
break;
|
||||
case LOP_FASTCALL2:
|
||||
emitInstFastCall2(build, pc, i, instLabels.data());
|
||||
break;
|
||||
case LOP_FASTCALL2K:
|
||||
emitInstFastCall2K(build, pc, i, instLabels.data());
|
||||
break;
|
||||
case LOP_FORNPREP:
|
||||
emitInstForNPrep(build, pc, i, instLabels.data());
|
||||
break;
|
||||
case LOP_FORNLOOP:
|
||||
emitInstForNLoop(build, pc, i, instLabels.data());
|
||||
break;
|
||||
case LOP_AND:
|
||||
emitInstAnd(build, pc);
|
||||
break;
|
||||
case LOP_ANDK:
|
||||
emitInstAndK(build, pc);
|
||||
break;
|
||||
case LOP_OR:
|
||||
emitInstOr(build, pc);
|
||||
break;
|
||||
case LOP_ORK:
|
||||
emitInstOrK(build, pc);
|
||||
break;
|
||||
case LOP_GETIMPORT:
|
||||
emitInstGetImport(build, pc, instFallbacks[i]);
|
||||
break;
|
||||
case LOP_CONCAT:
|
||||
emitInstConcat(build, pc, i, instLabels.data());
|
||||
break;
|
||||
default:
|
||||
emitFallback(build, data, op, i);
|
||||
break;
|
||||
}
|
||||
int skip = emitInst(build, data, helpers, proto, op, pc, i, instLabels.data(), instFallbacks[i]);
|
||||
|
||||
i += getOpLength(op);
|
||||
if (skip != 0)
|
||||
instOutlines.push_back({nexti, skip});
|
||||
|
||||
i = nexti + skip;
|
||||
LUAU_ASSERT(i <= proto->sizecode);
|
||||
}
|
||||
|
||||
size_t textSize = build.text.size();
|
||||
uint32_t codeSize = build.getCodeSize();
|
||||
|
||||
if (options.annotator && !options.skipOutlinedCode)
|
||||
build.logAppend("; outlined instructions\n");
|
||||
|
||||
for (auto [pcpos, length] : instOutlines)
|
||||
{
|
||||
int i = pcpos;
|
||||
|
||||
while (i < pcpos + length)
|
||||
{
|
||||
const Instruction* pc = &proto->code[i];
|
||||
LuauOpcode op = LuauOpcode(LUAU_INSN_OP(*pc));
|
||||
|
||||
build.setLabel(instLabels[i]);
|
||||
|
||||
if (options.annotator && !options.skipOutlinedCode)
|
||||
options.annotator(options.annotatorContext, build.text, proto->bytecodeid, i);
|
||||
|
||||
int skip = emitInst(build, data, helpers, proto, op, pc, i, instLabels.data(), instFallbacks[i]);
|
||||
LUAU_ASSERT(skip == 0);
|
||||
|
||||
i += getOpLength(op);
|
||||
}
|
||||
|
||||
if (i < proto->sizecode)
|
||||
build.jmp(instLabels[i]);
|
||||
}
|
||||
|
||||
if (options.annotator && !options.skipOutlinedCode)
|
||||
build.logAppend("; outlined code\n");
|
||||
|
||||
|
@ -297,104 +483,7 @@ static NativeProto* assembleFunction(AssemblyBuilderX64& build, NativeState& dat
|
|||
|
||||
build.setLabel(instFallbacks[i]);
|
||||
|
||||
switch (op)
|
||||
{
|
||||
case LOP_GETIMPORT:
|
||||
emitInstGetImportFallback(build, pc, i);
|
||||
break;
|
||||
case LOP_GETTABLE:
|
||||
emitInstGetTableFallback(build, pc, i);
|
||||
break;
|
||||
case LOP_SETTABLE:
|
||||
emitInstSetTableFallback(build, pc, i);
|
||||
break;
|
||||
case LOP_GETTABLEN:
|
||||
emitInstGetTableNFallback(build, pc, i);
|
||||
break;
|
||||
case LOP_SETTABLEN:
|
||||
emitInstSetTableNFallback(build, pc, i);
|
||||
break;
|
||||
case LOP_JUMPIFEQ:
|
||||
emitInstJumpIfEqFallback(build, pc, i, instLabels.data(), /* not_ */ false);
|
||||
break;
|
||||
case LOP_JUMPIFLE:
|
||||
emitInstJumpIfCondFallback(build, pc, i, instLabels.data(), Condition::LessEqual);
|
||||
break;
|
||||
case LOP_JUMPIFLT:
|
||||
emitInstJumpIfCondFallback(build, pc, i, instLabels.data(), Condition::Less);
|
||||
break;
|
||||
case LOP_JUMPIFNOTEQ:
|
||||
emitInstJumpIfEqFallback(build, pc, i, instLabels.data(), /* not_ */ true);
|
||||
break;
|
||||
case LOP_JUMPIFNOTLE:
|
||||
emitInstJumpIfCondFallback(build, pc, i, instLabels.data(), Condition::NotLessEqual);
|
||||
break;
|
||||
case LOP_JUMPIFNOTLT:
|
||||
emitInstJumpIfCondFallback(build, pc, i, instLabels.data(), Condition::NotLess);
|
||||
break;
|
||||
case LOP_ADD:
|
||||
emitInstBinaryFallback(build, pc, i, TM_ADD);
|
||||
break;
|
||||
case LOP_SUB:
|
||||
emitInstBinaryFallback(build, pc, i, TM_SUB);
|
||||
break;
|
||||
case LOP_MUL:
|
||||
emitInstBinaryFallback(build, pc, i, TM_MUL);
|
||||
break;
|
||||
case LOP_DIV:
|
||||
emitInstBinaryFallback(build, pc, i, TM_DIV);
|
||||
break;
|
||||
case LOP_MOD:
|
||||
emitInstBinaryFallback(build, pc, i, TM_MOD);
|
||||
break;
|
||||
case LOP_POW:
|
||||
emitInstBinaryFallback(build, pc, i, TM_POW);
|
||||
break;
|
||||
case LOP_ADDK:
|
||||
emitInstBinaryKFallback(build, pc, i, TM_ADD);
|
||||
break;
|
||||
case LOP_SUBK:
|
||||
emitInstBinaryKFallback(build, pc, i, TM_SUB);
|
||||
break;
|
||||
case LOP_MULK:
|
||||
emitInstBinaryKFallback(build, pc, i, TM_MUL);
|
||||
break;
|
||||
case LOP_DIVK:
|
||||
emitInstBinaryKFallback(build, pc, i, TM_DIV);
|
||||
break;
|
||||
case LOP_MODK:
|
||||
emitInstBinaryKFallback(build, pc, i, TM_MOD);
|
||||
break;
|
||||
case LOP_POWK:
|
||||
emitInstBinaryKFallback(build, pc, i, TM_POW);
|
||||
break;
|
||||
case LOP_MINUS:
|
||||
emitInstMinusFallback(build, pc, i);
|
||||
break;
|
||||
case LOP_LENGTH:
|
||||
emitInstLengthFallback(build, pc, i);
|
||||
break;
|
||||
case LOP_GETGLOBAL:
|
||||
// TODO: luaV_gettable + cachedslot update instead of full fallback
|
||||
emitFallback(build, data, op, i);
|
||||
break;
|
||||
case LOP_SETGLOBAL:
|
||||
// TODO: luaV_settable + cachedslot update instead of full fallback
|
||||
emitFallback(build, data, op, i);
|
||||
break;
|
||||
case LOP_GETTABLEKS:
|
||||
// Full fallback required for LOP_GETTABLEKS because 'luaV_gettable' doesn't handle builtin vector field access
|
||||
// It is also required to perform cached slot update
|
||||
// TODO: extra fast-paths could be lowered before the full fallback
|
||||
emitFallback(build, data, op, i);
|
||||
break;
|
||||
case LOP_SETTABLEKS:
|
||||
// TODO: luaV_settable + cachedslot update instead of full fallback
|
||||
emitFallback(build, data, op, i);
|
||||
break;
|
||||
default:
|
||||
LUAU_ASSERT(!"Expected fallback for instruction");
|
||||
}
|
||||
emitInstFallback(build, data, op, pc, i, instLabels.data());
|
||||
|
||||
// Jump back to the next instruction handler
|
||||
if (nexti < proto->sizecode)
|
||||
|
@ -568,13 +657,16 @@ void compile(lua_State* L, int idx)
|
|||
std::vector<Proto*> protos;
|
||||
gatherFunctions(protos, clvalue(func)->l.p);
|
||||
|
||||
ModuleHelpers helpers;
|
||||
assembleHelpers(build, helpers);
|
||||
|
||||
std::vector<NativeProto*> results;
|
||||
results.reserve(protos.size());
|
||||
|
||||
// Skip protos that have been compiled during previous invocations of CodeGen::compile
|
||||
for (Proto* p : protos)
|
||||
if (p && getProtoExecData(p) == nullptr)
|
||||
results.push_back(assembleFunction(build, *data, p, {}));
|
||||
results.push_back(assembleFunction(build, *data, helpers, p, {}));
|
||||
|
||||
build.finalize();
|
||||
|
||||
|
@ -615,10 +707,13 @@ std::string getAssembly(lua_State* L, int idx, AssemblyOptions options)
|
|||
std::vector<Proto*> protos;
|
||||
gatherFunctions(protos, clvalue(func)->l.p);
|
||||
|
||||
ModuleHelpers helpers;
|
||||
assembleHelpers(build, helpers);
|
||||
|
||||
for (Proto* p : protos)
|
||||
if (p)
|
||||
{
|
||||
NativeProto* nativeProto = assembleFunction(build, data, p, options);
|
||||
NativeProto* nativeProto = assembleFunction(build, data, helpers, p, options);
|
||||
destroyNativeProto(nativeProto);
|
||||
}
|
||||
|
||||
|
|
76
CodeGen/src/CodeGenUtils.cpp
Normal file
76
CodeGen/src/CodeGenUtils.cpp
Normal file
|
@ -0,0 +1,76 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "CodeGenUtils.h"
|
||||
|
||||
#include "ldo.h"
|
||||
#include "ltable.h"
|
||||
|
||||
#include "FallbacksProlog.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
namespace CodeGen
|
||||
{
|
||||
|
||||
bool forgLoopNodeIter(lua_State* L, Table* h, int index, TValue* ra)
|
||||
{
|
||||
// then we advance index through the hash portion
|
||||
while (unsigned(index - h->sizearray) < unsigned(1 << h->lsizenode))
|
||||
{
|
||||
LuaNode* n = &h->node[index - h->sizearray];
|
||||
|
||||
if (!ttisnil(gval(n)))
|
||||
{
|
||||
setpvalue(ra + 2, reinterpret_cast<void*>(uintptr_t(index + 1)));
|
||||
getnodekey(L, ra + 3, n);
|
||||
setobj(L, ra + 4, gval(n));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool forgLoopNonTableFallback(lua_State* L, int insnA, int aux)
|
||||
{
|
||||
TValue* base = L->base;
|
||||
TValue* ra = VM_REG(insnA);
|
||||
|
||||
// note: it's safe to push arguments past top for complicated reasons (see lvmexecute.cpp)
|
||||
setobj2s(L, ra + 3 + 2, ra + 2);
|
||||
setobj2s(L, ra + 3 + 1, ra + 1);
|
||||
setobj2s(L, ra + 3, ra);
|
||||
|
||||
L->top = ra + 3 + 3; // func + 2 args (state and index)
|
||||
LUAU_ASSERT(L->top <= L->stack_last);
|
||||
|
||||
luaD_call(L, ra + 3, uint8_t(aux));
|
||||
L->top = L->ci->top;
|
||||
|
||||
// recompute ra since stack might have been reallocated
|
||||
base = L->base;
|
||||
ra = VM_REG(insnA);
|
||||
|
||||
// copy first variable back into the iteration index
|
||||
setobj2s(L, ra + 2, ra + 3);
|
||||
|
||||
return !ttisnil(ra + 3);
|
||||
}
|
||||
|
||||
void forgPrepXnextFallback(lua_State* L, TValue* ra, int pc)
|
||||
{
|
||||
if (!ttisfunction(ra))
|
||||
{
|
||||
Closure* cl = clvalue(L->ci->func);
|
||||
L->ci->savedpc = cl->l.p->code + pc;
|
||||
|
||||
luaG_typeerror(L, ra, "iterate over");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace CodeGen
|
||||
} // namespace Luau
|
17
CodeGen/src/CodeGenUtils.h
Normal file
17
CodeGen/src/CodeGenUtils.h
Normal file
|
@ -0,0 +1,17 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "lobject.h"
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
namespace CodeGen
|
||||
{
|
||||
|
||||
bool forgLoopNodeIter(lua_State* L, Table* h, int index, TValue* ra);
|
||||
bool forgLoopNonTableFallback(lua_State* L, int insnA, int aux);
|
||||
|
||||
void forgPrepXnextFallback(lua_State* L, TValue* ra, int pc);
|
||||
|
||||
} // namespace CodeGen
|
||||
} // namespace Luau
|
|
@ -67,6 +67,13 @@ constexpr unsigned kLuaNodeTagMask = 0xf;
|
|||
constexpr unsigned kOffsetOfLuaNodeTag = 12; // offsetof cannot be used on a bit field
|
||||
constexpr unsigned kOffsetOfInstructionC = 3;
|
||||
|
||||
// Leaf functions that are placed in every module to perform common instruction sequences
|
||||
struct ModuleHelpers
|
||||
{
|
||||
Label exitContinueVm;
|
||||
Label exitNoContinueVm;
|
||||
};
|
||||
|
||||
inline OperandX64 luauReg(int ri)
|
||||
{
|
||||
return xmmword[rBase + ri * sizeof(TValue)];
|
||||
|
@ -107,6 +114,12 @@ inline OperandX64 luauNodeKeyValue(RegisterX64 node)
|
|||
return qword[node + offsetof(LuaNode, key) + offsetof(TKey, value)];
|
||||
}
|
||||
|
||||
// Note: tag has dirty upper bits
|
||||
inline OperandX64 luauNodeKeyTag(RegisterX64 node)
|
||||
{
|
||||
return dword[node + offsetof(LuaNode, key) + kOffsetOfLuaNodeTag];
|
||||
}
|
||||
|
||||
inline OperandX64 luauNodeValue(RegisterX64 node)
|
||||
{
|
||||
return xmmword[node + offsetof(LuaNode, val)];
|
||||
|
@ -184,7 +197,7 @@ inline void jumpIfNodeKeyTagIsNot(AssemblyBuilderX64& build, RegisterX64 tmp, Re
|
|||
{
|
||||
tmp.size = SizeX64::dword;
|
||||
|
||||
build.mov(tmp, dword[node + offsetof(LuaNode, key) + kOffsetOfLuaNodeTag]);
|
||||
build.mov(tmp, luauNodeKeyTag(node));
|
||||
build.and_(tmp, kLuaNodeTagMask);
|
||||
build.cmp(tmp, tag);
|
||||
build.jcc(Condition::NotEqual, label);
|
||||
|
|
|
@ -34,7 +34,7 @@ void emitInstLoadB(AssemblyBuilderX64& build, const Instruction* pc, int pcpos,
|
|||
build.mov(luauRegTag(ra), LUA_TBOOLEAN);
|
||||
|
||||
if (int target = LUAU_INSN_C(*pc))
|
||||
build.jmp(labelarr[pcpos + target + 1]);
|
||||
build.jmp(labelarr[pcpos + 1 + target]);
|
||||
}
|
||||
|
||||
void emitInstLoadN(AssemblyBuilderX64& build, const Instruction* pc)
|
||||
|
@ -72,23 +72,185 @@ void emitInstMove(AssemblyBuilderX64& build, const Instruction* pc)
|
|||
build.vmovups(luauReg(ra), xmm0);
|
||||
}
|
||||
|
||||
void emitInstReturn(AssemblyBuilderX64& build, ModuleHelpers& helpers, const Instruction* pc, int pcpos, Label* labelarr)
|
||||
{
|
||||
emitInterrupt(build, pcpos);
|
||||
|
||||
int ra = LUAU_INSN_A(*pc);
|
||||
int b = LUAU_INSN_B(*pc) - 1;
|
||||
|
||||
RegisterX64 ci = r8;
|
||||
RegisterX64 cip = r9;
|
||||
RegisterX64 res = rdi;
|
||||
RegisterX64 nresults = esi;
|
||||
|
||||
build.mov(ci, qword[rState + offsetof(lua_State, ci)]);
|
||||
build.lea(cip, qword[ci - sizeof(CallInfo)]);
|
||||
|
||||
// res = ci->func; note: we assume CALL always puts func+args and expects results to start at func
|
||||
build.mov(res, qword[ci + offsetof(CallInfo, func)]);
|
||||
// nresults = ci->nresults
|
||||
build.mov(nresults, dword[ci + offsetof(CallInfo, nresults)]);
|
||||
|
||||
{
|
||||
Label skipResultCopy;
|
||||
|
||||
RegisterX64 counter = ecx;
|
||||
|
||||
if (b == 0)
|
||||
{
|
||||
// Our instruction doesn't have any results, so just fill results expected in parent with 'nil'
|
||||
build.test(nresults, nresults); // test here will set SF=1 for a negative number, ZF=1 for zero and OF=0
|
||||
build.jcc(Condition::LessEqual, skipResultCopy); // jle jumps if SF != OF or ZF == 1
|
||||
|
||||
build.mov(counter, nresults);
|
||||
|
||||
Label repeatNilLoop = build.setLabel();
|
||||
build.mov(dword[res + offsetof(TValue, tt)], LUA_TNIL);
|
||||
build.add(res, sizeof(TValue));
|
||||
build.dec(counter);
|
||||
build.jcc(Condition::NotZero, repeatNilLoop);
|
||||
}
|
||||
else if (b == 1)
|
||||
{
|
||||
// Try setting our 1 result
|
||||
build.test(nresults, nresults);
|
||||
build.jcc(Condition::Zero, skipResultCopy);
|
||||
|
||||
build.lea(counter, dword[nresults - 1]);
|
||||
|
||||
build.vmovups(xmm0, luauReg(ra));
|
||||
build.vmovups(xmmword[res], xmm0);
|
||||
build.add(res, sizeof(TValue));
|
||||
|
||||
// Fill the rest of the expected results with 'nil'
|
||||
build.test(counter, counter); // test here will set SF=1 for a negative number, ZF=1 for zero and OF=0
|
||||
build.jcc(Condition::LessEqual, skipResultCopy); // jle jumps if SF != OF or ZF == 1
|
||||
|
||||
Label repeatNilLoop = build.setLabel();
|
||||
build.mov(dword[res + offsetof(TValue, tt)], LUA_TNIL);
|
||||
build.add(res, sizeof(TValue));
|
||||
build.dec(counter);
|
||||
build.jcc(Condition::NotZero, repeatNilLoop);
|
||||
}
|
||||
else
|
||||
{
|
||||
RegisterX64 vali = rax;
|
||||
RegisterX64 valend = rdx;
|
||||
|
||||
// Copy return values into parent stack (but only up to nresults!)
|
||||
build.test(nresults, nresults);
|
||||
build.jcc(Condition::Zero, skipResultCopy);
|
||||
|
||||
// vali = ra
|
||||
build.lea(vali, luauRegValue(ra));
|
||||
|
||||
// Copy as much as possible for MULTRET calls, and only as much as needed otherwise
|
||||
if (b == LUA_MULTRET)
|
||||
build.mov(valend, qword[rState + offsetof(lua_State, top)]); // valend = L->top
|
||||
else
|
||||
build.lea(valend, luauRegValue(ra + b)); // valend = ra + b
|
||||
|
||||
build.mov(counter, nresults);
|
||||
|
||||
Label repeatValueLoop, exitValueLoop;
|
||||
|
||||
build.setLabel(repeatValueLoop);
|
||||
build.cmp(vali, valend);
|
||||
build.jcc(Condition::NotBelow, exitValueLoop);
|
||||
|
||||
build.vmovups(xmm0, xmmword[vali]);
|
||||
build.vmovups(xmmword[res], xmm0);
|
||||
build.add(vali, sizeof(TValue));
|
||||
build.add(res, sizeof(TValue));
|
||||
build.dec(counter);
|
||||
build.jcc(Condition::NotZero, repeatValueLoop);
|
||||
|
||||
build.setLabel(exitValueLoop);
|
||||
|
||||
// Fill the rest of the expected results with 'nil'
|
||||
build.test(counter, counter); // test here will set SF=1 for a negative number, ZF=1 for zero and OF=0
|
||||
build.jcc(Condition::LessEqual, skipResultCopy); // jle jumps if SF != OF or ZF == 1
|
||||
|
||||
Label repeatNilLoop = build.setLabel();
|
||||
build.mov(dword[res + offsetof(TValue, tt)], LUA_TNIL);
|
||||
build.add(res, sizeof(TValue));
|
||||
build.dec(counter);
|
||||
build.jcc(Condition::NotZero, repeatNilLoop);
|
||||
}
|
||||
|
||||
build.setLabel(skipResultCopy);
|
||||
}
|
||||
|
||||
build.mov(qword[rState + offsetof(lua_State, ci)], cip); // L->ci = cip
|
||||
build.mov(rBase, qword[cip + offsetof(CallInfo, base)]); // sync base = L->base while we have a chance
|
||||
build.mov(qword[rState + offsetof(lua_State, base)], rBase); // L->base = cip->base
|
||||
|
||||
// Start with result for LUA_MULTRET/exit value
|
||||
build.mov(qword[rState + offsetof(lua_State, top)], res); // L->top = res
|
||||
|
||||
// Unlikely, but this might be the last return from VM
|
||||
build.test(byte[ci + offsetof(CallInfo, flags)], LUA_CALLINFO_RETURN);
|
||||
build.jcc(Condition::NotZero, helpers.exitNoContinueVm);
|
||||
|
||||
Label skipFixedRetTop;
|
||||
build.test(nresults, nresults); // test here will set SF=1 for a negative number and it always sets OF to 0
|
||||
build.jcc(Condition::Less, skipFixedRetTop); // jl jumps if SF != OF
|
||||
build.mov(rax, qword[cip + offsetof(CallInfo, top)]);
|
||||
build.mov(qword[rState + offsetof(lua_State, top)], rax); // L->top = cip->top
|
||||
build.setLabel(skipFixedRetTop);
|
||||
|
||||
// Returning back to the previous function is a bit tricky
|
||||
// Registers alive: r9 (cip)
|
||||
RegisterX64 proto = rcx;
|
||||
RegisterX64 execdata = rbx;
|
||||
|
||||
// Change closure
|
||||
build.mov(rax, qword[cip + offsetof(CallInfo, func)]);
|
||||
build.mov(rax, qword[rax + offsetof(TValue, value.gc)]);
|
||||
build.mov(sClosure, rax);
|
||||
|
||||
build.mov(proto, qword[rax + offsetof(Closure, l.p)]);
|
||||
|
||||
build.mov(execdata, qword[proto + offsetofProtoExecData]);
|
||||
build.test(execdata, execdata);
|
||||
build.jcc(Condition::Zero, helpers.exitContinueVm); // Continue in interpreter if function has no native data
|
||||
|
||||
// Change constants
|
||||
build.mov(rConstants, qword[proto + offsetof(Proto, k)]);
|
||||
|
||||
// Change code
|
||||
build.mov(rdx, qword[proto + offsetof(Proto, code)]);
|
||||
build.mov(sCode, rdx);
|
||||
|
||||
build.mov(rax, qword[cip + offsetof(CallInfo, savedpc)]);
|
||||
|
||||
// To get instruction index from instruction pointer, we need to divide byte offset by 4
|
||||
// But we will actually need to scale instruction index by 8 back to byte offset later so it cancels out
|
||||
build.sub(rax, rdx);
|
||||
|
||||
// Get new instruction location and jump to it
|
||||
build.mov(rdx, qword[execdata + offsetof(NativeProto, instTargets)]);
|
||||
build.jmp(qword[rdx + rax * 2]);
|
||||
}
|
||||
|
||||
void emitInstJump(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label* labelarr)
|
||||
{
|
||||
build.jmp(labelarr[pcpos + LUAU_INSN_D(*pc) + 1]);
|
||||
build.jmp(labelarr[pcpos + 1 + LUAU_INSN_D(*pc)]);
|
||||
}
|
||||
|
||||
void emitInstJumpBack(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label* labelarr)
|
||||
{
|
||||
emitInterrupt(build, pcpos);
|
||||
|
||||
build.jmp(labelarr[pcpos + LUAU_INSN_D(*pc) + 1]);
|
||||
build.jmp(labelarr[pcpos + 1 + LUAU_INSN_D(*pc)]);
|
||||
}
|
||||
|
||||
void emitInstJumpIf(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label* labelarr, bool not_)
|
||||
{
|
||||
int ra = LUAU_INSN_A(*pc);
|
||||
|
||||
Label& target = labelarr[pcpos + LUAU_INSN_D(*pc) + 1];
|
||||
Label& target = labelarr[pcpos + 1 + LUAU_INSN_D(*pc)];
|
||||
Label& exit = labelarr[pcpos + 1];
|
||||
|
||||
if (not_)
|
||||
|
@ -102,7 +264,7 @@ void emitInstJumpIfEq(AssemblyBuilderX64& build, const Instruction* pc, int pcpo
|
|||
int ra = LUAU_INSN_A(*pc);
|
||||
int rb = pc[1];
|
||||
|
||||
Label& target = labelarr[pcpos + LUAU_INSN_D(*pc) + 1];
|
||||
Label& target = labelarr[pcpos + 1 + LUAU_INSN_D(*pc)];
|
||||
Label& exit = labelarr[pcpos + 2];
|
||||
|
||||
build.mov(eax, luauRegTag(ra));
|
||||
|
@ -121,7 +283,7 @@ void emitInstJumpIfEq(AssemblyBuilderX64& build, const Instruction* pc, int pcpo
|
|||
|
||||
void emitInstJumpIfEqFallback(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label* labelarr, bool not_)
|
||||
{
|
||||
Label& target = labelarr[pcpos + LUAU_INSN_D(*pc) + 1];
|
||||
Label& target = labelarr[pcpos + 1 + LUAU_INSN_D(*pc)];
|
||||
|
||||
jumpOnAnyCmpFallback(build, LUAU_INSN_A(*pc), pc[1], not_ ? Condition::NotEqual : Condition::Equal, target, pcpos);
|
||||
}
|
||||
|
@ -131,7 +293,7 @@ void emitInstJumpIfCond(AssemblyBuilderX64& build, const Instruction* pc, int pc
|
|||
int ra = LUAU_INSN_A(*pc);
|
||||
int rb = pc[1];
|
||||
|
||||
Label& target = labelarr[pcpos + LUAU_INSN_D(*pc) + 1];
|
||||
Label& target = labelarr[pcpos + 1 + LUAU_INSN_D(*pc)];
|
||||
|
||||
// fast-path: number
|
||||
jumpIfTagIsNot(build, ra, LUA_TNUMBER, fallback);
|
||||
|
@ -142,7 +304,7 @@ void emitInstJumpIfCond(AssemblyBuilderX64& build, const Instruction* pc, int pc
|
|||
|
||||
void emitInstJumpIfCondFallback(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label* labelarr, Condition cond)
|
||||
{
|
||||
Label& target = labelarr[pcpos + LUAU_INSN_D(*pc) + 1];
|
||||
Label& target = labelarr[pcpos + 1 + LUAU_INSN_D(*pc)];
|
||||
|
||||
jumpOnAnyCmpFallback(build, LUAU_INSN_A(*pc), pc[1], cond, target, pcpos);
|
||||
}
|
||||
|
@ -151,7 +313,7 @@ void emitInstJumpX(AssemblyBuilderX64& build, const Instruction* pc, int pcpos,
|
|||
{
|
||||
emitInterrupt(build, pcpos);
|
||||
|
||||
build.jmp(labelarr[pcpos + LUAU_INSN_E(*pc) + 1]);
|
||||
build.jmp(labelarr[pcpos + 1 + LUAU_INSN_E(*pc)]);
|
||||
}
|
||||
|
||||
void emitInstJumpxEqNil(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label* labelarr)
|
||||
|
@ -159,7 +321,7 @@ void emitInstJumpxEqNil(AssemblyBuilderX64& build, const Instruction* pc, int pc
|
|||
int ra = LUAU_INSN_A(*pc);
|
||||
bool not_ = (pc[1] & 0x80000000) != 0;
|
||||
|
||||
Label& target = labelarr[pcpos + LUAU_INSN_D(*pc) + 1];
|
||||
Label& target = labelarr[pcpos + 1 + LUAU_INSN_D(*pc)];
|
||||
|
||||
build.cmp(luauRegTag(ra), LUA_TNIL);
|
||||
build.jcc(not_ ? Condition::NotEqual : Condition::Equal, target);
|
||||
|
@ -171,7 +333,7 @@ void emitInstJumpxEqB(AssemblyBuilderX64& build, const Instruction* pc, int pcpo
|
|||
uint32_t aux = pc[1];
|
||||
bool not_ = (aux & 0x80000000) != 0;
|
||||
|
||||
Label& target = labelarr[pcpos + LUAU_INSN_D(*pc) + 1];
|
||||
Label& target = labelarr[pcpos + 1 + LUAU_INSN_D(*pc)];
|
||||
Label& exit = labelarr[pcpos + 2];
|
||||
|
||||
jumpIfTagIsNot(build, ra, LUA_TBOOLEAN, not_ ? target : exit);
|
||||
|
@ -187,7 +349,7 @@ void emitInstJumpxEqN(AssemblyBuilderX64& build, const Instruction* pc, const TV
|
|||
bool not_ = (aux & 0x80000000) != 0;
|
||||
TValue kv = k[aux & 0xffffff];
|
||||
|
||||
Label& target = labelarr[pcpos + LUAU_INSN_D(*pc) + 1];
|
||||
Label& target = labelarr[pcpos + 1 + LUAU_INSN_D(*pc)];
|
||||
Label& exit = labelarr[pcpos + 2];
|
||||
|
||||
jumpIfTagIsNot(build, ra, LUA_TNUMBER, not_ ? target : exit);
|
||||
|
@ -212,7 +374,7 @@ void emitInstJumpxEqS(AssemblyBuilderX64& build, const Instruction* pc, int pcpo
|
|||
uint32_t aux = pc[1];
|
||||
bool not_ = (aux & 0x80000000) != 0;
|
||||
|
||||
Label& target = labelarr[pcpos + LUAU_INSN_D(*pc) + 1];
|
||||
Label& target = labelarr[pcpos + 1 + LUAU_INSN_D(*pc)];
|
||||
Label& exit = labelarr[pcpos + 2];
|
||||
|
||||
jumpIfTagIsNot(build, ra, LUA_TSTRING, not_ ? target : exit);
|
||||
|
@ -596,8 +758,8 @@ void emitInstCloseUpvals(AssemblyBuilderX64& build, const Instruction* pc, int p
|
|||
build.call(qword[rNativeContext + offsetof(NativeContext, luaF_close)]);
|
||||
}
|
||||
|
||||
static void emitInstFastCallN(
|
||||
AssemblyBuilderX64& build, const Instruction* pc, bool customParams, int customParamCount, OperandX64 customArgs, int pcpos, Label* labelarr)
|
||||
static int emitInstFastCallN(AssemblyBuilderX64& build, const Instruction* pc, bool customParams, int customParamCount, OperandX64 customArgs,
|
||||
int pcpos, int instLen, Label* labelarr)
|
||||
{
|
||||
int bfid = LUAU_INSN_A(*pc);
|
||||
int skip = LUAU_INSN_C(*pc);
|
||||
|
@ -611,7 +773,7 @@ static void emitInstFastCallN(
|
|||
int arg = customParams ? LUAU_INSN_B(*pc) : ra + 1;
|
||||
OperandX64 args = customParams ? customArgs : luauRegValue(ra + 2);
|
||||
|
||||
Label exit;
|
||||
Label& exit = labelarr[pcpos + instLen];
|
||||
|
||||
jumpIfUnsafeEnv(build, rax, exit);
|
||||
|
||||
|
@ -633,10 +795,7 @@ static void emitInstFastCallN(
|
|||
build.mov(qword[rState + offsetof(lua_State, top)], rax);
|
||||
}
|
||||
|
||||
// TODO: once we start outlining the fallback, we will be able to fallthrough to the next instruction
|
||||
build.jmp(labelarr[pcpos + skip + 2]);
|
||||
build.setLabel(exit);
|
||||
return;
|
||||
return skip + 2 - instLen; // Return fallback instruction sequence length
|
||||
}
|
||||
|
||||
// TODO: we can skip saving pc for some well-behaved builtins which we didn't inline
|
||||
|
@ -705,37 +864,35 @@ static void emitInstFastCallN(
|
|||
build.mov(qword[rState + offsetof(lua_State, top)], rax);
|
||||
}
|
||||
|
||||
build.jmp(labelarr[pcpos + skip + 2]);
|
||||
|
||||
build.setLabel(exit);
|
||||
|
||||
// TODO: fallback to LOP_CALL after a fast call should be outlined
|
||||
return skip + 2 - instLen; // Return fallback instruction sequence length
|
||||
}
|
||||
|
||||
void emitInstFastCall1(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label* labelarr)
|
||||
int emitInstFastCall1(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label* labelarr)
|
||||
{
|
||||
emitInstFastCallN(build, pc, /* customParams */ true, /* customParamCount */ 1, /* customArgs */ 0, pcpos, labelarr);
|
||||
return emitInstFastCallN(build, pc, /* customParams */ true, /* customParamCount */ 1, /* customArgs */ 0, pcpos, /* instLen */ 1, labelarr);
|
||||
}
|
||||
|
||||
void emitInstFastCall2(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label* labelarr)
|
||||
int emitInstFastCall2(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label* labelarr)
|
||||
{
|
||||
emitInstFastCallN(build, pc, /* customParams */ true, /* customParamCount */ 2, /* customArgs */ luauRegValue(pc[1]), pcpos, labelarr);
|
||||
return emitInstFastCallN(
|
||||
build, pc, /* customParams */ true, /* customParamCount */ 2, /* customArgs */ luauRegValue(pc[1]), pcpos, /* instLen */ 2, labelarr);
|
||||
}
|
||||
|
||||
void emitInstFastCall2K(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label* labelarr)
|
||||
int emitInstFastCall2K(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label* labelarr)
|
||||
{
|
||||
emitInstFastCallN(build, pc, /* customParams */ true, /* customParamCount */ 2, /* customArgs */ luauConstantValue(pc[1]), pcpos, labelarr);
|
||||
return emitInstFastCallN(
|
||||
build, pc, /* customParams */ true, /* customParamCount */ 2, /* customArgs */ luauConstantValue(pc[1]), pcpos, /* instLen */ 2, labelarr);
|
||||
}
|
||||
|
||||
void emitInstFastCall(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label* labelarr)
|
||||
int emitInstFastCall(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label* labelarr)
|
||||
{
|
||||
emitInstFastCallN(build, pc, /* customParams */ false, /* customParamCount */ 0, /* customArgs */ 0, pcpos, labelarr);
|
||||
return emitInstFastCallN(build, pc, /* customParams */ false, /* customParamCount */ 0, /* customArgs */ 0, pcpos, /* instLen */ 1, labelarr);
|
||||
}
|
||||
|
||||
void emitInstForNPrep(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label* labelarr)
|
||||
{
|
||||
int ra = LUAU_INSN_A(*pc);
|
||||
Label& loopExit = labelarr[pcpos + LUAU_INSN_D(*pc) + 1];
|
||||
Label& loopExit = labelarr[pcpos + 1 + LUAU_INSN_D(*pc)];
|
||||
|
||||
Label tryConvert, exit;
|
||||
|
||||
|
@ -784,7 +941,7 @@ void emitInstForNLoop(AssemblyBuilderX64& build, const Instruction* pc, int pcpo
|
|||
emitInterrupt(build, pcpos);
|
||||
|
||||
int ra = LUAU_INSN_A(*pc);
|
||||
Label& loopRepeat = labelarr[pcpos + LUAU_INSN_D(*pc) + 1];
|
||||
Label& loopRepeat = labelarr[pcpos + 1 + LUAU_INSN_D(*pc)];
|
||||
|
||||
RegisterX64 limit = xmm0;
|
||||
RegisterX64 step = xmm1;
|
||||
|
@ -814,6 +971,162 @@ void emitInstForNLoop(AssemblyBuilderX64& build, const Instruction* pc, int pcpo
|
|||
build.setLabel(exit);
|
||||
}
|
||||
|
||||
void emitinstForGLoop(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label* labelarr, Label& fallback)
|
||||
{
|
||||
int ra = LUAU_INSN_A(*pc);
|
||||
int aux = pc[1];
|
||||
|
||||
Label& loopRepeat = labelarr[pcpos + 1 + LUAU_INSN_D(*pc)];
|
||||
Label& exit = labelarr[pcpos + 2];
|
||||
|
||||
emitInterrupt(build, pcpos);
|
||||
|
||||
// fast-path: builtin table iteration
|
||||
jumpIfTagIsNot(build, ra, LUA_TNIL, fallback);
|
||||
|
||||
// Registers are chosen in this way to simplify fallback code for the node part
|
||||
RegisterX64 table = rArg2;
|
||||
RegisterX64 index = rArg3;
|
||||
RegisterX64 elemPtr = rax;
|
||||
|
||||
build.mov(table, luauRegValue(ra + 1));
|
||||
build.mov(index, luauRegValue(ra + 2));
|
||||
|
||||
// &array[index]
|
||||
build.mov(dwordReg(elemPtr), dwordReg(index));
|
||||
build.shl(dwordReg(elemPtr), kTValueSizeLog2);
|
||||
build.add(elemPtr, qword[table + offsetof(Table, array)]);
|
||||
|
||||
// Clear extra variables since we might have more than two
|
||||
for (int i = 2; i < aux; ++i)
|
||||
build.mov(luauRegTag(ra + 3 + i), LUA_TNIL);
|
||||
|
||||
// ipairs-style traversal is terminated early when array part ends of nil array element is encountered
|
||||
bool isIpairsIter = aux < 0;
|
||||
|
||||
Label skipArray, skipArrayNil;
|
||||
|
||||
// First we advance index through the array portion
|
||||
// while (unsigned(index) < unsigned(sizearray))
|
||||
Label arrayLoop = build.setLabel();
|
||||
build.cmp(dwordReg(index), dword[table + offsetof(Table, sizearray)]);
|
||||
build.jcc(Condition::NotBelow, isIpairsIter ? exit : skipArray);
|
||||
|
||||
// If element is nil, we increment the index; if it's not, we still need 'index + 1' inside
|
||||
build.inc(index);
|
||||
|
||||
build.cmp(dword[elemPtr + offsetof(TValue, tt)], LUA_TNIL);
|
||||
build.jcc(Condition::Equal, isIpairsIter ? exit : skipArrayNil);
|
||||
|
||||
// setpvalue(ra + 2, reinterpret_cast<void*>(uintptr_t(index + 1)));
|
||||
build.mov(luauRegValue(ra + 2), index);
|
||||
// Tag should already be set to lightuserdata
|
||||
|
||||
// setnvalue(ra + 3, double(index + 1));
|
||||
build.vcvtsi2sd(xmm0, xmm0, dwordReg(index));
|
||||
build.vmovsd(luauRegValue(ra + 3), xmm0);
|
||||
build.mov(luauRegTag(ra + 3), LUA_TNUMBER);
|
||||
|
||||
// setobj2s(L, ra + 4, e);
|
||||
setLuauReg(build, xmm2, ra + 4, xmmword[elemPtr]);
|
||||
|
||||
build.jmp(loopRepeat);
|
||||
|
||||
if (!isIpairsIter)
|
||||
{
|
||||
build.setLabel(skipArrayNil);
|
||||
|
||||
// Index already incremented, advance to next array element
|
||||
build.add(elemPtr, sizeof(TValue));
|
||||
build.jmp(arrayLoop);
|
||||
|
||||
build.setLabel(skipArray);
|
||||
|
||||
// Call helper to assign next node value or to signal loop exit
|
||||
build.mov(rArg1, rState);
|
||||
// rArg2 and rArg3 are already set
|
||||
build.lea(rArg4, luauRegValue(ra));
|
||||
build.call(qword[rNativeContext + offsetof(NativeContext, forgLoopNodeIter)]);
|
||||
build.test(al, al);
|
||||
build.jcc(Condition::NotZero, loopRepeat);
|
||||
}
|
||||
}
|
||||
|
||||
void emitinstForGLoopFallback(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label* labelarr)
|
||||
{
|
||||
int ra = LUAU_INSN_A(*pc);
|
||||
int aux = pc[1];
|
||||
|
||||
Label& loopRepeat = labelarr[pcpos + 1 + LUAU_INSN_D(*pc)];
|
||||
|
||||
emitSetSavedPc(build, pcpos + 1);
|
||||
|
||||
build.mov(rArg1, rState);
|
||||
build.mov(rArg2, ra);
|
||||
build.mov(rArg3, aux);
|
||||
build.call(qword[rNativeContext + offsetof(NativeContext, forgLoopNonTableFallback)]);
|
||||
emitUpdateBase(build);
|
||||
build.test(al, al);
|
||||
build.jcc(Condition::NotZero, loopRepeat);
|
||||
}
|
||||
|
||||
void emitInstForGPrepNext(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label* labelarr, Label& fallback)
|
||||
{
|
||||
int ra = LUAU_INSN_A(*pc);
|
||||
|
||||
Label& target = labelarr[pcpos + 1 + LUAU_INSN_D(*pc)];
|
||||
|
||||
// fast-path: pairs/next
|
||||
jumpIfUnsafeEnv(build, rax, fallback);
|
||||
jumpIfTagIsNot(build, ra + 1, LUA_TTABLE, fallback);
|
||||
jumpIfTagIsNot(build, ra + 2, LUA_TNIL, fallback);
|
||||
|
||||
build.mov(luauRegTag(ra), LUA_TNIL);
|
||||
|
||||
// setpvalue(ra + 2, reinterpret_cast<void*>(uintptr_t(0)));
|
||||
build.mov(luauRegValue(ra + 2), 0);
|
||||
build.mov(luauRegTag(ra + 2), LUA_TLIGHTUSERDATA);
|
||||
|
||||
build.jmp(target);
|
||||
}
|
||||
|
||||
void emitInstForGPrepInext(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label* labelarr, Label& fallback)
|
||||
{
|
||||
int ra = LUAU_INSN_A(*pc);
|
||||
|
||||
Label& target = labelarr[pcpos + 1 + LUAU_INSN_D(*pc)];
|
||||
|
||||
// fast-path: ipairs/inext
|
||||
jumpIfUnsafeEnv(build, rax, fallback);
|
||||
jumpIfTagIsNot(build, ra + 1, LUA_TTABLE, fallback);
|
||||
jumpIfTagIsNot(build, ra + 2, LUA_TNUMBER, fallback);
|
||||
|
||||
build.vxorpd(xmm0, xmm0, xmm0);
|
||||
build.vmovsd(xmm1, luauRegValue(ra + 2));
|
||||
jumpOnNumberCmp(build, noreg, xmm0, xmm1, Condition::NotEqual, fallback);
|
||||
|
||||
build.mov(luauRegTag(ra), LUA_TNIL);
|
||||
|
||||
// setpvalue(ra + 2, reinterpret_cast<void*>(uintptr_t(0)));
|
||||
build.mov(luauRegValue(ra + 2), 0);
|
||||
build.mov(luauRegTag(ra + 2), LUA_TLIGHTUSERDATA);
|
||||
|
||||
build.jmp(target);
|
||||
}
|
||||
|
||||
void emitInstForGPrepXnextFallback(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label* labelarr)
|
||||
{
|
||||
int ra = LUAU_INSN_A(*pc);
|
||||
|
||||
Label& target = labelarr[pcpos + 1 + LUAU_INSN_D(*pc)];
|
||||
|
||||
build.mov(rArg1, rState);
|
||||
build.lea(rArg2, luauRegValue(ra));
|
||||
build.mov(rArg3, pcpos + 1);
|
||||
build.call(qword[rNativeContext + offsetof(NativeContext, forgPrepXnextFallback)]);
|
||||
build.jmp(target);
|
||||
}
|
||||
|
||||
static void emitInstAndX(AssemblyBuilderX64& build, int ra, int rb, OperandX64 c)
|
||||
{
|
||||
Label target, fallthrough;
|
||||
|
|
|
@ -16,7 +16,7 @@ namespace CodeGen
|
|||
class AssemblyBuilderX64;
|
||||
enum class Condition;
|
||||
struct Label;
|
||||
struct NativeState;
|
||||
struct ModuleHelpers;
|
||||
|
||||
void emitInstLoadNil(AssemblyBuilderX64& build, const Instruction* pc);
|
||||
void emitInstLoadB(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label* labelarr);
|
||||
|
@ -24,6 +24,7 @@ void emitInstLoadN(AssemblyBuilderX64& build, const Instruction* pc);
|
|||
void emitInstLoadK(AssemblyBuilderX64& build, const Instruction* pc);
|
||||
void emitInstLoadKX(AssemblyBuilderX64& build, const Instruction* pc);
|
||||
void emitInstMove(AssemblyBuilderX64& build, const Instruction* pc);
|
||||
void emitInstReturn(AssemblyBuilderX64& build, ModuleHelpers& helpers, const Instruction* pc, int pcpos, Label* labelarr);
|
||||
void emitInstJump(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label* labelarr);
|
||||
void emitInstJumpBack(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label* labelarr);
|
||||
void emitInstJumpIf(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label* labelarr, bool not_);
|
||||
|
@ -52,12 +53,17 @@ void emitInstSetList(AssemblyBuilderX64& build, const Instruction* pc, int pcpos
|
|||
void emitInstGetUpval(AssemblyBuilderX64& build, const Instruction* pc, int pcpos);
|
||||
void emitInstSetUpval(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label* labelarr);
|
||||
void emitInstCloseUpvals(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label* labelarr);
|
||||
void emitInstFastCall1(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label* labelarr);
|
||||
void emitInstFastCall2(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label* labelarr);
|
||||
void emitInstFastCall2K(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label* labelarr);
|
||||
void emitInstFastCall(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label* labelarr);
|
||||
int emitInstFastCall1(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label* labelarr);
|
||||
int emitInstFastCall2(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label* labelarr);
|
||||
int emitInstFastCall2K(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label* labelarr);
|
||||
int emitInstFastCall(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label* labelarr);
|
||||
void emitInstForNPrep(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label* labelarr);
|
||||
void emitInstForNLoop(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label* labelarr);
|
||||
void emitinstForGLoop(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label* labelarr, Label& fallback);
|
||||
void emitinstForGLoopFallback(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label* labelarr);
|
||||
void emitInstForGPrepNext(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label* labelarr, Label& fallback);
|
||||
void emitInstForGPrepInext(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label* labelarr, Label& fallback);
|
||||
void emitInstForGPrepXnextFallback(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label* labelarr);
|
||||
void emitInstAnd(AssemblyBuilderX64& build, const Instruction* pc);
|
||||
void emitInstAndK(AssemblyBuilderX64& build, const Instruction* pc);
|
||||
void emitInstOr(AssemblyBuilderX64& build, const Instruction* pc);
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include "Luau/UnwindBuilder.h"
|
||||
|
||||
#include "CodeGenUtils.h"
|
||||
#include "CustomExecUtils.h"
|
||||
#include "Fallbacks.h"
|
||||
|
||||
|
@ -13,14 +14,10 @@
|
|||
#include "lvm.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
|
||||
#define CODEGEN_SET_FALLBACK(op, flags) data.context.fallback[op] = {execute_##op, flags}
|
||||
|
||||
static int luauF_missing(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
namespace CodeGen
|
||||
|
@ -42,11 +39,7 @@ void initFallbackTable(NativeState& data)
|
|||
CODEGEN_SET_FALLBACK(LOP_NEWCLOSURE, 0);
|
||||
CODEGEN_SET_FALLBACK(LOP_NAMECALL, 0);
|
||||
CODEGEN_SET_FALLBACK(LOP_CALL, kFallbackUpdateCi | kFallbackCheckInterrupt);
|
||||
CODEGEN_SET_FALLBACK(LOP_RETURN, kFallbackUpdateCi | kFallbackCheckInterrupt);
|
||||
CODEGEN_SET_FALLBACK(LOP_FORGPREP, kFallbackUpdatePc);
|
||||
CODEGEN_SET_FALLBACK(LOP_FORGLOOP, kFallbackUpdatePc | kFallbackCheckInterrupt);
|
||||
CODEGEN_SET_FALLBACK(LOP_FORGPREP_INEXT, kFallbackUpdatePc);
|
||||
CODEGEN_SET_FALLBACK(LOP_FORGPREP_NEXT, kFallbackUpdatePc);
|
||||
CODEGEN_SET_FALLBACK(LOP_GETVARARGS, 0);
|
||||
CODEGEN_SET_FALLBACK(LOP_DUPCLOSURE, 0);
|
||||
CODEGEN_SET_FALLBACK(LOP_PREPVARARGS, 0);
|
||||
|
@ -62,12 +55,8 @@ void initFallbackTable(NativeState& data)
|
|||
|
||||
void initHelperFunctions(NativeState& data)
|
||||
{
|
||||
static_assert(sizeof(data.context.luauF_table) / sizeof(data.context.luauF_table[0]) == sizeof(luauF_table) / sizeof(luauF_table[0]),
|
||||
"fast call tables are not of the same length");
|
||||
|
||||
// Replace missing fast call functions with an empty placeholder that forces LOP_CALL fallback
|
||||
for (size_t i = 0; i < sizeof(data.context.luauF_table) / sizeof(data.context.luauF_table[0]); i++)
|
||||
data.context.luauF_table[i] = luauF_table[i] ? luauF_table[i] : luauF_missing;
|
||||
static_assert(sizeof(data.context.luauF_table) == sizeof(luauF_table), "fastcall tables are not of the same length");
|
||||
memcpy(data.context.luauF_table, luauF_table, sizeof(luauF_table));
|
||||
|
||||
data.context.luaV_lessthan = luaV_lessthan;
|
||||
data.context.luaV_lessequal = luaV_lessequal;
|
||||
|
@ -93,6 +82,10 @@ void initHelperFunctions(NativeState& data)
|
|||
data.context.luaF_close = luaF_close;
|
||||
|
||||
data.context.libm_pow = pow;
|
||||
|
||||
data.context.forgLoopNodeIter = forgLoopNodeIter;
|
||||
data.context.forgLoopNonTableFallback = forgLoopNonTableFallback;
|
||||
data.context.forgPrepXnextFallback = forgPrepXnextFallback;
|
||||
}
|
||||
|
||||
} // namespace CodeGen
|
||||
|
|
|
@ -3,13 +3,16 @@
|
|||
|
||||
#include "Luau/Bytecode.h"
|
||||
#include "Luau/CodeAllocator.h"
|
||||
#include "Luau/Label.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "ldebug.h"
|
||||
#include "lobject.h"
|
||||
#include "ltm.h"
|
||||
#include "lstate.h"
|
||||
|
||||
typedef int (*luau_FastFunction)(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams);
|
||||
|
||||
|
@ -77,6 +80,11 @@ struct NativeContext
|
|||
void (*luaF_close)(lua_State* L, StkId level) = nullptr;
|
||||
|
||||
double (*libm_pow)(double, double) = nullptr;
|
||||
|
||||
// Helper functions
|
||||
bool (*forgLoopNodeIter)(lua_State* L, Table* h, int index, TValue* ra) = nullptr;
|
||||
bool (*forgLoopNonTableFallback)(lua_State* L, int insnA, int aux) = nullptr;
|
||||
void (*forgPrepXnextFallback)(lua_State* L, TValue* ra, int pc) = nullptr;
|
||||
};
|
||||
|
||||
struct NativeState
|
||||
|
|
|
@ -117,7 +117,7 @@ public:
|
|||
std::string dumpEverything() const;
|
||||
std::string dumpSourceRemarks() const;
|
||||
|
||||
void annotateInstruction(std::string& result, uint32_t fid, uint32_t instid) const;
|
||||
void annotateInstruction(std::string& result, uint32_t fid, uint32_t instpos) const;
|
||||
|
||||
static uint32_t getImportId(int32_t id0);
|
||||
static uint32_t getImportId(int32_t id0, int32_t id1);
|
||||
|
|
|
@ -1977,14 +1977,14 @@ std::string BytecodeBuilder::dumpCurrentFunction(std::vector<int>& dumpinstoffs)
|
|||
if (labels[i] == 0)
|
||||
labels[i] = nextLabel++;
|
||||
|
||||
dumpinstoffs.reserve(insns.size());
|
||||
dumpinstoffs.resize(insns.size() + 1, -1);
|
||||
|
||||
for (size_t i = 0; i < insns.size();)
|
||||
{
|
||||
const uint32_t* code = &insns[i];
|
||||
uint8_t op = LUAU_INSN_OP(*code);
|
||||
|
||||
dumpinstoffs.push_back(int(result.size()));
|
||||
dumpinstoffs[i] = int(result.size());
|
||||
|
||||
if (op == LOP_PREPVARARGS)
|
||||
{
|
||||
|
@ -2028,7 +2028,7 @@ std::string BytecodeBuilder::dumpCurrentFunction(std::vector<int>& dumpinstoffs)
|
|||
LUAU_ASSERT(i <= insns.size());
|
||||
}
|
||||
|
||||
dumpinstoffs.push_back(int(result.size()));
|
||||
dumpinstoffs[insns.size()] = int(result.size());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
@ -2119,7 +2119,7 @@ std::string BytecodeBuilder::dumpSourceRemarks() const
|
|||
return result;
|
||||
}
|
||||
|
||||
void BytecodeBuilder::annotateInstruction(std::string& result, uint32_t fid, uint32_t instid) const
|
||||
void BytecodeBuilder::annotateInstruction(std::string& result, uint32_t fid, uint32_t instpos) const
|
||||
{
|
||||
if ((dumpFlags & Dump_Code) == 0)
|
||||
return;
|
||||
|
@ -2130,9 +2130,15 @@ void BytecodeBuilder::annotateInstruction(std::string& result, uint32_t fid, uin
|
|||
const std::string& dump = function.dump;
|
||||
const std::vector<int>& dumpinstoffs = function.dumpinstoffs;
|
||||
|
||||
LUAU_ASSERT(instid + 1 < dumpinstoffs.size());
|
||||
uint32_t next = instpos + 1;
|
||||
|
||||
formatAppend(result, "%.*s", dumpinstoffs[instid + 1] - dumpinstoffs[instid], dump.data() + dumpinstoffs[instid]);
|
||||
LUAU_ASSERT(next < dumpinstoffs.size());
|
||||
|
||||
// Skip locations of multi-dword instructions
|
||||
while (next < dumpinstoffs.size() && dumpinstoffs[next] == -1)
|
||||
next++;
|
||||
|
||||
formatAppend(result, "%.*s", dumpinstoffs[next] - dumpinstoffs[instpos], dump.data() + dumpinstoffs[instpos]);
|
||||
}
|
||||
|
||||
} // namespace Luau
|
||||
|
|
4
Makefile
4
Makefile
|
@ -75,7 +75,7 @@ endif
|
|||
|
||||
# configuration-specific flags
|
||||
ifeq ($(config),release)
|
||||
CXXFLAGS+=-O2 -DNDEBUG
|
||||
CXXFLAGS+=-O2 -DNDEBUG -fno-math-errno
|
||||
endif
|
||||
|
||||
ifeq ($(config),coverage)
|
||||
|
@ -102,7 +102,7 @@ ifeq ($(config),fuzz)
|
|||
endif
|
||||
|
||||
ifeq ($(config),profile)
|
||||
CXXFLAGS+=-O2 -DNDEBUG -gdwarf-4 -DCALLGRIND=1
|
||||
CXXFLAGS+=-O2 -DNDEBUG -fno-math-errno -gdwarf-4 -DCALLGRIND=1
|
||||
endif
|
||||
|
||||
ifeq ($(protobuf),download)
|
||||
|
|
|
@ -71,6 +71,7 @@ target_sources(Luau.CodeGen PRIVATE
|
|||
CodeGen/src/CodeAllocator.cpp
|
||||
CodeGen/src/CodeBlockUnwind.cpp
|
||||
CodeGen/src/CodeGen.cpp
|
||||
CodeGen/src/CodeGenUtils.cpp
|
||||
CodeGen/src/CodeGenX64.cpp
|
||||
CodeGen/src/EmitBuiltinsX64.cpp
|
||||
CodeGen/src/EmitCommonX64.cpp
|
||||
|
@ -82,6 +83,7 @@ target_sources(Luau.CodeGen PRIVATE
|
|||
|
||||
CodeGen/src/ByteUtils.h
|
||||
CodeGen/src/CustomExecUtils.h
|
||||
CodeGen/src/CodeGenUtils.h
|
||||
CodeGen/src/CodeGenX64.h
|
||||
CodeGen/src/EmitBuiltinsX64.h
|
||||
CodeGen/src/EmitCommonX64.h
|
||||
|
@ -339,6 +341,7 @@ if(TARGET Luau.UnitTest)
|
|||
tests/TypeInfer.intersectionTypes.test.cpp
|
||||
tests/TypeInfer.loops.test.cpp
|
||||
tests/TypeInfer.modules.test.cpp
|
||||
tests/TypeInfer.negations.test.cpp
|
||||
tests/TypeInfer.oop.test.cpp
|
||||
tests/TypeInfer.operators.test.cpp
|
||||
tests/TypeInfer.primitives.test.cpp
|
||||
|
|
|
@ -20,6 +20,14 @@
|
|||
#define LUAU_FASTMATH_END
|
||||
#endif
|
||||
|
||||
// Some functions like floor/ceil have SSE4.1 equivalents but we currently support systems without SSE4.1
|
||||
// On newer GCC and Clang we can use function multi-versioning to generate SSE4.1 code plus CPUID based dispatch.
|
||||
#if !defined(__APPLE__) && (defined(__x86_64__) || defined(_M_X64)) && ((defined(__clang__) && __clang_major__ >= 14) || (defined(__GNUC__) && __GNUC__ >= 6)) && !defined(__SSE4_1__)
|
||||
#define LUAU_DISPATCH_SSE41 __attribute__((target_clones("default", "sse4.1")))
|
||||
#else
|
||||
#define LUAU_DISPATCH_SSE41
|
||||
#endif
|
||||
|
||||
// Used on functions that have a printf-like interface to validate them statically
|
||||
#if defined(__GNUC__)
|
||||
#define LUA_PRINTF_ATTR(fmt, arg) __attribute__((format(printf, fmt, arg)))
|
||||
|
|
|
@ -96,6 +96,7 @@ static int luauF_atan(lua_State* L, StkId res, TValue* arg0, int nresults, StkId
|
|||
}
|
||||
|
||||
LUAU_FASTMATH_BEGIN
|
||||
LUAU_DISPATCH_SSE41
|
||||
static int luauF_ceil(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams)
|
||||
{
|
||||
if (nparams >= 1 && nresults <= 1 && ttisnumber(arg0))
|
||||
|
@ -159,6 +160,7 @@ static int luauF_exp(lua_State* L, StkId res, TValue* arg0, int nresults, StkId
|
|||
}
|
||||
|
||||
LUAU_FASTMATH_BEGIN
|
||||
LUAU_DISPATCH_SSE41
|
||||
static int luauF_floor(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams)
|
||||
{
|
||||
if (nparams >= 1 && nresults <= 1 && ttisnumber(arg0))
|
||||
|
@ -936,6 +938,7 @@ static int luauF_sign(lua_State* L, StkId res, TValue* arg0, int nresults, StkId
|
|||
}
|
||||
|
||||
LUAU_FASTMATH_BEGIN
|
||||
LUAU_DISPATCH_SSE41
|
||||
static int luauF_round(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams)
|
||||
{
|
||||
if (nparams >= 1 && nresults <= 1 && ttisnumber(arg0))
|
||||
|
@ -1239,7 +1242,12 @@ static int luauF_setmetatable(lua_State* L, StkId res, TValue* arg0, int nresult
|
|||
return -1;
|
||||
}
|
||||
|
||||
luau_FastFunction luauF_table[256] = {
|
||||
static int luauF_missing(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
const luau_FastFunction luauF_table[256] = {
|
||||
NULL,
|
||||
luauF_assert,
|
||||
|
||||
|
@ -1317,4 +1325,20 @@ luau_FastFunction luauF_table[256] = {
|
|||
|
||||
luauF_getmetatable,
|
||||
luauF_setmetatable,
|
||||
|
||||
// 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.
|
||||
#define MISSING8 luauF_missing, luauF_missing, luauF_missing, luauF_missing, luauF_missing, luauF_missing, luauF_missing, luauF_missing
|
||||
|
||||
MISSING8,
|
||||
MISSING8,
|
||||
MISSING8,
|
||||
MISSING8,
|
||||
MISSING8,
|
||||
MISSING8,
|
||||
MISSING8,
|
||||
MISSING8,
|
||||
|
||||
#undef MISSING8
|
||||
};
|
||||
|
|
|
@ -6,4 +6,4 @@
|
|||
|
||||
typedef int (*luau_FastFunction)(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams);
|
||||
|
||||
extern luau_FastFunction luauF_table[256];
|
||||
extern const luau_FastFunction luauF_table[256];
|
||||
|
|
|
@ -34,6 +34,7 @@ inline bool luai_vecisnan(const float* a)
|
|||
}
|
||||
|
||||
LUAU_FASTMATH_BEGIN
|
||||
// TODO: LUAU_DISPATCH_SSE41 would be nice here, but clang-14 doesn't support it correctly on inline functions...
|
||||
inline double luai_nummod(double a, double b)
|
||||
{
|
||||
return a - floor(a / b) * b;
|
||||
|
|
|
@ -16,8 +16,6 @@
|
|||
|
||||
#include <string.h>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauNoTopRestoreInFastCall, false)
|
||||
|
||||
// Disable c99-designator to avoid the warning in CGOTO dispatch table
|
||||
#ifdef __clang__
|
||||
#if __has_warning("-Wc99-designator")
|
||||
|
@ -2094,12 +2092,17 @@ reentry:
|
|||
|
||||
Table* h = hvalue(ra);
|
||||
|
||||
// TODO: we really don't need this anymore
|
||||
if (!ttistable(ra))
|
||||
return; // temporary workaround to weaken a rather powerful exploitation primitive in case of a MITM attack on bytecode
|
||||
|
||||
int last = index + c - 1;
|
||||
if (last > h->sizearray)
|
||||
{
|
||||
VM_PROTECT_PC(); // luaH_resizearray may fail due to OOM
|
||||
|
||||
luaH_resizearray(L, h, last);
|
||||
}
|
||||
|
||||
TValue* array = h->array;
|
||||
|
||||
|
@ -2542,8 +2545,9 @@ reentry:
|
|||
nparams = (nparams == LUA_MULTRET) ? int(L->top - ra - 1) : nparams;
|
||||
|
||||
luau_FastFunction f = luauF_table[bfid];
|
||||
LUAU_ASSERT(f);
|
||||
|
||||
if (cl->env->safeenv && f)
|
||||
if (cl->env->safeenv)
|
||||
{
|
||||
VM_PROTECT_PC(); // f may fail due to OOM
|
||||
|
||||
|
@ -2620,8 +2624,9 @@ reentry:
|
|||
int nresults = LUAU_INSN_C(call) - 1;
|
||||
|
||||
luau_FastFunction f = luauF_table[bfid];
|
||||
LUAU_ASSERT(f);
|
||||
|
||||
if (cl->env->safeenv && f)
|
||||
if (cl->env->safeenv)
|
||||
{
|
||||
VM_PROTECT_PC(); // f may fail due to OOM
|
||||
|
||||
|
@ -2629,15 +2634,8 @@ reentry:
|
|||
|
||||
if (n >= 0)
|
||||
{
|
||||
if (FFlag::LuauNoTopRestoreInFastCall)
|
||||
{
|
||||
if (nresults == LUA_MULTRET)
|
||||
L->top = ra + n;
|
||||
}
|
||||
else
|
||||
{
|
||||
L->top = (nresults == LUA_MULTRET) ? ra + n : L->ci->top;
|
||||
}
|
||||
if (nresults == LUA_MULTRET)
|
||||
L->top = ra + n;
|
||||
|
||||
pc += skip + 1; // skip instructions that compute function as well as CALL
|
||||
LUAU_ASSERT(unsigned(pc - cl->l.p->code) < unsigned(cl->l.p->sizecode));
|
||||
|
@ -2676,8 +2674,9 @@ reentry:
|
|||
int nresults = LUAU_INSN_C(call) - 1;
|
||||
|
||||
luau_FastFunction f = luauF_table[bfid];
|
||||
LUAU_ASSERT(f);
|
||||
|
||||
if (cl->env->safeenv && f)
|
||||
if (cl->env->safeenv)
|
||||
{
|
||||
VM_PROTECT_PC(); // f may fail due to OOM
|
||||
|
||||
|
@ -2685,15 +2684,8 @@ reentry:
|
|||
|
||||
if (n >= 0)
|
||||
{
|
||||
if (FFlag::LuauNoTopRestoreInFastCall)
|
||||
{
|
||||
if (nresults == LUA_MULTRET)
|
||||
L->top = ra + n;
|
||||
}
|
||||
else
|
||||
{
|
||||
L->top = (nresults == LUA_MULTRET) ? ra + n : L->ci->top;
|
||||
}
|
||||
if (nresults == LUA_MULTRET)
|
||||
L->top = ra + n;
|
||||
|
||||
pc += skip + 1; // skip instructions that compute function as well as CALL
|
||||
LUAU_ASSERT(unsigned(pc - cl->l.p->code) < unsigned(cl->l.p->sizecode));
|
||||
|
@ -2732,8 +2724,9 @@ reentry:
|
|||
int nresults = LUAU_INSN_C(call) - 1;
|
||||
|
||||
luau_FastFunction f = luauF_table[bfid];
|
||||
LUAU_ASSERT(f);
|
||||
|
||||
if (cl->env->safeenv && f)
|
||||
if (cl->env->safeenv)
|
||||
{
|
||||
VM_PROTECT_PC(); // f may fail due to OOM
|
||||
|
||||
|
@ -2741,15 +2734,8 @@ reentry:
|
|||
|
||||
if (n >= 0)
|
||||
{
|
||||
if (FFlag::LuauNoTopRestoreInFastCall)
|
||||
{
|
||||
if (nresults == LUA_MULTRET)
|
||||
L->top = ra + n;
|
||||
}
|
||||
else
|
||||
{
|
||||
L->top = (nresults == LUA_MULTRET) ? ra + n : L->ci->top;
|
||||
}
|
||||
if (nresults == LUA_MULTRET)
|
||||
L->top = ra + n;
|
||||
|
||||
pc += skip + 1; // skip instructions that compute function as well as CALL
|
||||
LUAU_ASSERT(unsigned(pc - cl->l.p->code) < unsigned(cl->l.p->sizecode));
|
||||
|
|
456
bench/tests/voxelgen.lua
Normal file
456
bench/tests/voxelgen.lua
Normal file
|
@ -0,0 +1,456 @@
|
|||
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
||||
|
||||
-- Based on voxel terrain generator by Stickmasterluke
|
||||
|
||||
local kSelectedBiomes = {
|
||||
['Mountains'] = true,
|
||||
['Canyons'] = true,
|
||||
['Dunes'] = true,
|
||||
['Arctic'] = true,
|
||||
['Lavaflow'] = true,
|
||||
['Hills'] = true,
|
||||
['Plains'] = true,
|
||||
['Marsh'] = true,
|
||||
['Water'] = true,
|
||||
}
|
||||
|
||||
---------Directly used in Generation---------
|
||||
local masterSeed = 618033988
|
||||
local mapWidth = 32
|
||||
local mapHeight = 32
|
||||
local biomeSize = 16
|
||||
local generateCaves = true
|
||||
local waterLevel = .48
|
||||
local surfaceThickness = .018
|
||||
local biomes = {}
|
||||
---------------------------------------------
|
||||
|
||||
local rock = "Rock"
|
||||
local snow = "Snow"
|
||||
local ice = "Glacier"
|
||||
local grass = "Grass"
|
||||
local ground = "Ground"
|
||||
local mud = "Mud"
|
||||
local slate = "Slate"
|
||||
local concrete = "Concrete"
|
||||
local lava = "CrackedLava"
|
||||
local basalt = "Basalt"
|
||||
local air = "Air"
|
||||
local sand = "Sand"
|
||||
local sandstone = "Sandstone"
|
||||
local water = "Water"
|
||||
|
||||
math.randomseed(6180339)
|
||||
local theseed={}
|
||||
for i=1,999 do
|
||||
table.insert(theseed,math.random())
|
||||
end
|
||||
|
||||
local function getPerlin(x,y,z,seed,scale,raw)
|
||||
local seed = seed or 0
|
||||
local scale = scale or 1
|
||||
if not raw then
|
||||
return math.noise(x/scale+(seed*17)+masterSeed,y/scale-masterSeed,z/scale-seed*seed)*.5 + .5 -- accounts for bleeding from interpolated line
|
||||
else
|
||||
return math.noise(x/scale+(seed*17)+masterSeed,y/scale-masterSeed,z/scale-seed*seed)
|
||||
end
|
||||
end
|
||||
|
||||
local function getNoise(x,y,z,seed1)
|
||||
local x = x or 0
|
||||
local y = y or 0
|
||||
local z = z or 0
|
||||
local seed1 = seed1 or 7
|
||||
local wtf=x+y+z+seed1+masterSeed + (masterSeed-x)*(seed1+z) + (seed1-y)*(masterSeed+z) -- + x*(y+z) + z*(masterSeed+seed1) + seed1*(x+y) --x+y+z+seed1+masterSeed + x*y*masterSeed-y*z+(z+masterSeed)*x --((x+y)*(y-seed1)*seed1)-(x+z)*seed2+x*11+z*23-y*17
|
||||
return theseed[(math.floor(wtf%(#theseed)))+1]
|
||||
end
|
||||
|
||||
local function thresholdFilter(value, bottom, size)
|
||||
if value <= bottom then
|
||||
return 0
|
||||
elseif value >= bottom+size then
|
||||
return 1
|
||||
else
|
||||
return (value-bottom)/size
|
||||
end
|
||||
end
|
||||
|
||||
local function ridgedFilter(value) --absolute and flip for ridges. and normalize
|
||||
return value<.5 and value*2 or 2-value*2
|
||||
end
|
||||
|
||||
local function ridgedFlippedFilter(value) --unflipped
|
||||
return value < .5 and 1-value*2 or value*2-1
|
||||
end
|
||||
|
||||
local function advancedRidgedFilter(value, cutoff)
|
||||
local cutoff = cutoff or .5
|
||||
value = value - cutoff
|
||||
return 1 - (value < 0 and -value or value) * 1/(1-cutoff)
|
||||
end
|
||||
|
||||
local function fractalize(operation,x,y,z, operationCount, scale, offset, gain)
|
||||
local operationCount = operationCount or 3
|
||||
local scale = scale or .5
|
||||
local offset = 0
|
||||
local gain = gain or 1
|
||||
local totalValue = 0
|
||||
local totalScale = 0
|
||||
for i=1, operationCount do
|
||||
local thisScale = scale^(i-1)
|
||||
totalScale = totalScale + thisScale
|
||||
totalValue = totalValue + (offset + gain * operation(x,y,z,i))*thisScale
|
||||
end
|
||||
return totalValue/totalScale
|
||||
end
|
||||
|
||||
local function mountainsOperation(x,y,z,i)
|
||||
return ridgedFilter(getPerlin(x,y,z,100+i,(1/i)*160))
|
||||
end
|
||||
|
||||
local canyonBandingMaterial = {rock,mud,sand,sand,sandstone,sandstone,sandstone,sandstone,sandstone,sandstone,}
|
||||
|
||||
local function findBiomeInfo(choiceBiome,x,y,z,verticalGradientTurbulence)
|
||||
local choiceBiomeValue = .5
|
||||
local choiceBiomeSurface = grass
|
||||
local choiceBiomeFill = rock
|
||||
if choiceBiome == 'City' then
|
||||
choiceBiomeValue = .55
|
||||
choiceBiomeSurface = concrete
|
||||
choiceBiomeFill = slate
|
||||
elseif choiceBiome == 'Water' then
|
||||
choiceBiomeValue = .36+getPerlin(x,y,z,2,50)*.08
|
||||
choiceBiomeSurface =
|
||||
(1-verticalGradientTurbulence < .44 and slate)
|
||||
or sand
|
||||
elseif choiceBiome == 'Marsh' then
|
||||
local preLedge = getPerlin(x+getPerlin(x,0,z,5,7,true)*10+getPerlin(x,0,z,6,30,true)*50,0,z+getPerlin(x,0,z,9,7,true)*10+getPerlin(x,0,z,10,30,true)*50,2,70) --could use some turbulence
|
||||
local grassyLedge = thresholdFilter(preLedge,.65,0)
|
||||
local largeGradient = getPerlin(x,y,z,4,100)
|
||||
local smallGradient = getPerlin(x,y,z,3,20)
|
||||
local smallGradientThreshold = thresholdFilter(smallGradient,.5,0)
|
||||
choiceBiomeValue = waterLevel-.04
|
||||
+preLedge*grassyLedge*.025
|
||||
+largeGradient*.035
|
||||
+smallGradient*.025
|
||||
choiceBiomeSurface =
|
||||
(grassyLedge >= 1 and grass)
|
||||
or (1-verticalGradientTurbulence < waterLevel-.01 and mud)
|
||||
or (1-verticalGradientTurbulence < waterLevel+.01 and ground)
|
||||
or grass
|
||||
choiceBiomeFill = slate
|
||||
elseif choiceBiome == 'Plains' then
|
||||
local rivulet = ridgedFlippedFilter(getPerlin(x+getPerlin(x,y,z,17,40)*25,0,z+getPerlin(x,y,z,19,40)*25,2,200))
|
||||
local rivuletThreshold = thresholdFilter(rivulet,.01,0)
|
||||
|
||||
local rockMap = thresholdFilter(ridgedFlippedFilter(getPerlin(x,0,z,101,7)),.3,.7) --rocks
|
||||
* thresholdFilter(getPerlin(x,0,z,102,50),.6,.05) --zoning
|
||||
|
||||
choiceBiomeValue = .5 --.51
|
||||
+getPerlin(x,y,z,2,100)*.02 --.05
|
||||
+rivulet*.05 --.02
|
||||
+rockMap*.05 --.03
|
||||
+rivuletThreshold*.005
|
||||
|
||||
local verticalGradient = 1-((y-1)/(mapHeight-1))
|
||||
local surfaceGradient = verticalGradient*.5 + choiceBiomeValue*.5
|
||||
local thinSurface = surfaceGradient > .5-surfaceThickness*.4 and surfaceGradient < .5+surfaceThickness*.4
|
||||
choiceBiomeSurface =
|
||||
(rockMap>0 and rock)
|
||||
or (not thinSurface and mud)
|
||||
or (thinSurface and rivuletThreshold <=0 and water)
|
||||
or (1-verticalGradientTurbulence < waterLevel-.01 and sand)
|
||||
or grass
|
||||
choiceBiomeFill =
|
||||
(rockMap>0 and rock)
|
||||
or sandstone
|
||||
elseif choiceBiome == 'Canyons' then
|
||||
local canyonNoise = ridgedFlippedFilter(getPerlin(x,0,z,2,200))
|
||||
local canyonNoiseTurbed = ridgedFlippedFilter(getPerlin(x+getPerlin(x,0,z,5,20,true)*20,0,z+getPerlin(x,0,z,9,20,true)*20,2,200))
|
||||
local sandbank = thresholdFilter(canyonNoiseTurbed,0,.05)
|
||||
local canyonTop = thresholdFilter(canyonNoiseTurbed,.125,0)
|
||||
local mesaSlope = thresholdFilter(canyonNoise,.33,.12)
|
||||
local mesaTop = thresholdFilter(canyonNoiseTurbed,.49,0)
|
||||
choiceBiomeValue = .42
|
||||
+getPerlin(x,y,z,2,70)*.05
|
||||
+canyonNoise*.05
|
||||
+sandbank*.04 --canyon bottom slope
|
||||
+thresholdFilter(canyonNoiseTurbed,.05,0)*.08 --canyon cliff
|
||||
+thresholdFilter(canyonNoiseTurbed,.05,.075)*.04 --canyon cliff top slope
|
||||
+canyonTop*.01 --canyon cliff top ledge
|
||||
|
||||
+thresholdFilter(canyonNoiseTurbed,.0575,.2725)*.01 --plane slope
|
||||
|
||||
+mesaSlope*.06 --mesa slope
|
||||
+thresholdFilter(canyonNoiseTurbed,.45,0)*.14 --mesa cliff
|
||||
+thresholdFilter(canyonNoiseTurbed,.45,.04)*.025 --mesa cap
|
||||
+mesaTop*.02 --mesa top ledge
|
||||
choiceBiomeSurface =
|
||||
(1-verticalGradientTurbulence < waterLevel+.015 and sand) --this for biome blending in to lakes
|
||||
or (sandbank>0 and sandbank<1 and sand) --this for canyonbase sandbanks
|
||||
--or (canyonTop>0 and canyonTop<=1 and mesaSlope<=0 and grass) --this for grassy canyon tops
|
||||
--or (mesaTop>0 and mesaTop<=1 and grass) --this for grassy mesa tops
|
||||
or sandstone
|
||||
choiceBiomeFill = canyonBandingMaterial[math.ceil((1-getNoise(1,y,2))*10)]
|
||||
elseif choiceBiome == 'Hills' then
|
||||
local rivulet = ridgedFlippedFilter(getPerlin(x+getPerlin(x,y,z,17,20)*20,0,z+getPerlin(x,y,z,19,20)*20,2,200))^(1/2)
|
||||
local largeHills = getPerlin(x,y,z,3,60)
|
||||
choiceBiomeValue = .48
|
||||
+largeHills*.05
|
||||
+(.05
|
||||
+largeHills*.1
|
||||
+getPerlin(x,y,z,4,25)*.125)
|
||||
*rivulet
|
||||
local surfaceMaterialGradient = (1-verticalGradientTurbulence)*.9 + rivulet*.1
|
||||
choiceBiomeSurface =
|
||||
(surfaceMaterialGradient < waterLevel-.015 and mud)
|
||||
or (surfaceMaterialGradient < waterLevel and ground)
|
||||
or grass
|
||||
choiceBiomeFill = slate
|
||||
elseif choiceBiome == 'Dunes' then
|
||||
local duneTurbulence = getPerlin(x,0,z,227,20)*24
|
||||
local layer1 = ridgedFilter(getPerlin(x,0,z,201,40))
|
||||
local layer2 = ridgedFilter(getPerlin(x/10+duneTurbulence,0,z+duneTurbulence,200,48))
|
||||
choiceBiomeValue = .4+.1*(layer1 + layer2)
|
||||
choiceBiomeSurface = sand
|
||||
choiceBiomeFill = sandstone
|
||||
elseif choiceBiome == 'Mountains' then
|
||||
local rivulet = ridgedFlippedFilter(getPerlin(x+getPerlin(x,y,z,17,20)*20,0,z+getPerlin(x,y,z,19,20)*20,2,200))
|
||||
choiceBiomeValue = -.4 --.3
|
||||
+fractalize(mountainsOperation,x,y/20,z, 8, .65)*1.2
|
||||
+rivulet*.2
|
||||
choiceBiomeSurface =
|
||||
(verticalGradientTurbulence < .275 and snow)
|
||||
or (verticalGradientTurbulence < .35 and rock)
|
||||
or (verticalGradientTurbulence < .4 and ground)
|
||||
or (1-verticalGradientTurbulence < waterLevel and rock)
|
||||
or (1-verticalGradientTurbulence < waterLevel+.01 and mud)
|
||||
or (1-verticalGradientTurbulence < waterLevel+.015 and ground)
|
||||
or grass
|
||||
elseif choiceBiome == 'Lavaflow' then
|
||||
local crackX = x+getPerlin(x,y*.25,z,21,8,true)*5
|
||||
local crackY = y+getPerlin(x,y*.25,z,22,8,true)*5
|
||||
local crackZ = z+getPerlin(x,y*.25,z,23,8,true)*5
|
||||
local crack1 = ridgedFilter(getPerlin(crackX+getPerlin(x,y,z,22,30,true)*30,crackY,crackZ+getPerlin(x,y,z,24,30,true)*30,2,120))
|
||||
local crack2 = ridgedFilter(getPerlin(crackX,crackY,crackZ,3,40))*(crack1*.25+.75)
|
||||
local crack3 = ridgedFilter(getPerlin(crackX,crackY,crackZ,4,20))*(crack2*.25+.75)
|
||||
|
||||
local generalHills = thresholdFilter(getPerlin(x,y,z,9,40),.25,.5)*getPerlin(x,y,z,10,60)
|
||||
|
||||
local cracks = math.max(0,1-thresholdFilter(crack1,.975,0)-thresholdFilter(crack2,.925,0)-thresholdFilter(crack3,.9,0))
|
||||
|
||||
local spires = thresholdFilter(getPerlin(crackX/40,crackY/300,crackZ/30,123,1),.6,.4)
|
||||
|
||||
choiceBiomeValue = waterLevel+.02
|
||||
+cracks*(.5+generalHills*.5)*.02
|
||||
+generalHills*.05
|
||||
+spires*.3
|
||||
+((1-verticalGradientTurbulence > waterLevel+.01 or spires>0) and .04 or 0) --This lets it lip over water
|
||||
|
||||
choiceBiomeFill = (spires>0 and rock) or (cracks<1 and lava) or basalt
|
||||
choiceBiomeSurface = (choiceBiomeFill == lava and 1-verticalGradientTurbulence < waterLevel and basalt) or choiceBiomeFill
|
||||
elseif choiceBiome == 'Arctic' then
|
||||
local preBoundary = getPerlin(x+getPerlin(x,0,z,5,8,true)*5,y/8,z+getPerlin(x,0,z,9,8,true)*5,2,20)
|
||||
--local cliffs = thresholdFilter(preBoundary,.5,0)
|
||||
local boundary = ridgedFilter(preBoundary)
|
||||
local roughChunks = getPerlin(x,y/4,z,436,2)
|
||||
local boundaryMask = thresholdFilter(boundary,.8,.1) --,.7,.25)
|
||||
local boundaryTypeMask = getPerlin(x,0,z,6,74)-.5
|
||||
local boundaryComp = 0
|
||||
if boundaryTypeMask < 0 then --divergent
|
||||
boundaryComp = (boundary > (1+boundaryTypeMask*.5) and -.17 or 0)
|
||||
--* boundaryTypeMask*-2
|
||||
else --convergent
|
||||
boundaryComp = boundaryMask*.1*roughChunks
|
||||
* boundaryTypeMask
|
||||
end
|
||||
choiceBiomeValue = .55
|
||||
+boundary*.05*boundaryTypeMask --.1 --soft slope up or down to boundary
|
||||
+boundaryComp --convergent/divergent effects
|
||||
+getPerlin(x,0,z,123,25)*.025 --*cliffs --gentle rolling slopes
|
||||
|
||||
choiceBiomeSurface = (1-verticalGradientTurbulence < waterLevel-.1 and ice) or (boundaryMask>.6 and boundaryTypeMask>.1 and roughChunks>.5 and ice) or snow
|
||||
choiceBiomeFill = ice
|
||||
end
|
||||
return choiceBiomeValue, choiceBiomeSurface, choiceBiomeFill
|
||||
end
|
||||
|
||||
function findBiomeTransitionValue(biome,weight,value,averageValue)
|
||||
if biome == 'Arctic' then
|
||||
return (weight>.2 and 1 or 0)*value
|
||||
elseif biome == 'Canyons' then
|
||||
return (weight>.7 and 1 or 0)*value
|
||||
elseif biome == 'Mountains' then
|
||||
local weight = weight^3 --This improves the ease of mountains transitioning to other biomes
|
||||
return averageValue*(1-weight)+value*weight
|
||||
else
|
||||
return averageValue*(1-weight)+value*weight
|
||||
end
|
||||
end
|
||||
|
||||
function generate()
|
||||
local mapWidth = mapWidth
|
||||
local biomeSize = biomeSize
|
||||
local biomeBlendPercent = .25 --(biomeSize==50 or biomeSize == 100) and .5 or .25
|
||||
local biomeBlendPercentInverse = 1-biomeBlendPercent
|
||||
local biomeBlendDistortion = biomeBlendPercent
|
||||
local smoothScale = .5/mapHeight
|
||||
|
||||
biomes = {}
|
||||
for i,v in pairs(kSelectedBiomes) do
|
||||
if v then
|
||||
table.insert(biomes,i)
|
||||
end
|
||||
end
|
||||
if #biomes<=0 then
|
||||
table.insert(biomes,'Hills')
|
||||
end
|
||||
table.sort(biomes)
|
||||
--local oMap = {}
|
||||
--local mMap = {}
|
||||
for x = 1, mapWidth do
|
||||
local oMapX = {}
|
||||
--oMap[x] = oMapX
|
||||
local mMapX = {}
|
||||
--mMap[x] = mMapX
|
||||
for z = 1, mapWidth do
|
||||
local biomeNoCave = false
|
||||
local cellToBiomeX = x/biomeSize + getPerlin(x,0,z,233,biomeSize*.3)*.25 + getPerlin(x,0,z,235,biomeSize*.05)*.075
|
||||
local cellToBiomeZ = z/biomeSize + getPerlin(x,0,z,234,biomeSize*.3)*.25 + getPerlin(x,0,z,236,biomeSize*.05)*.075
|
||||
local closestDistance = 1000000
|
||||
local biomePoints = {}
|
||||
for vx=-1,1 do
|
||||
for vz=-1,1 do
|
||||
local gridPointX = math.floor(cellToBiomeX+vx+.5)
|
||||
local gridPointZ = math.floor(cellToBiomeZ+vz+.5)
|
||||
--local pointX, pointZ = getBiomePoint(gridPointX,gridPointZ)
|
||||
local pointX = gridPointX+(getNoise(gridPointX,gridPointZ,53)-.5)*.75 --de-uniforming grid for vornonoi
|
||||
local pointZ = gridPointZ+(getNoise(gridPointX,gridPointZ,73)-.5)*.75
|
||||
|
||||
local dist = math.sqrt((pointX-cellToBiomeX)^2 + (pointZ-cellToBiomeZ)^2)
|
||||
if dist < closestDistance then
|
||||
closestDistance = dist
|
||||
end
|
||||
table.insert(biomePoints,{
|
||||
x = pointX,
|
||||
z = pointZ,
|
||||
dist = dist,
|
||||
biomeNoise = getNoise(gridPointX,gridPointZ),
|
||||
weight = 0
|
||||
})
|
||||
end
|
||||
end
|
||||
local weightTotal = 0
|
||||
local weightPoints = {}
|
||||
for _,point in pairs(biomePoints) do
|
||||
local weight = point.dist == closestDistance and 1 or ((closestDistance / point.dist)-biomeBlendPercentInverse)/biomeBlendPercent
|
||||
if weight > 0 then
|
||||
local weight = weight^2.1 --this smooths the biome transition from linear to cubic InOut
|
||||
weightTotal = weightTotal + weight
|
||||
local biome = biomes[math.ceil(#biomes*(1-point.biomeNoise))] --inverting the noise so that it is limited as (0,1]. One less addition operation when finding a random list index
|
||||
weightPoints[biome] = {
|
||||
weight = weightPoints[biome] and weightPoints[biome].weight + weight or weight
|
||||
}
|
||||
end
|
||||
end
|
||||
for biome,info in pairs(weightPoints) do
|
||||
info.weight = info.weight / weightTotal
|
||||
if biome == 'Arctic' then --biomes that don't have caves that breach the surface
|
||||
biomeNoCave = true
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
for y = 1, mapHeight do
|
||||
local oMapY = oMapX[y] or {}
|
||||
oMapX[y] = oMapY
|
||||
local mMapY = mMapX[y] or {}
|
||||
mMapX[y] = mMapY
|
||||
|
||||
--[[local oMapY = {}
|
||||
oMapX[y] = oMapY
|
||||
local mMapY = {}
|
||||
mMapX[z] = mMapY]]
|
||||
|
||||
|
||||
local verticalGradient = 1-((y-1)/(mapHeight-1))
|
||||
local caves = 0
|
||||
local verticalGradientTurbulence = verticalGradient*.9 + .1*getPerlin(x,y,z,107,15)
|
||||
local choiceValue = 0
|
||||
local choiceSurface = lava
|
||||
local choiceFill = rock
|
||||
|
||||
if verticalGradient > .65 or verticalGradient < .1 then
|
||||
--under surface of every biome; don't get biome data; waste of time.
|
||||
choiceValue = .5
|
||||
elseif #biomes == 1 then
|
||||
choiceValue, choiceSurface, choiceFill = findBiomeInfo(biomes[1],x,y,z,verticalGradientTurbulence)
|
||||
else
|
||||
local averageValue = 0
|
||||
--local findChoiceMaterial = -getNoise(x,y,z,19)
|
||||
for biome,info in pairs(weightPoints) do
|
||||
local biomeValue, biomeSurface, biomeFill = findBiomeInfo(biome,x,y,z,verticalGradientTurbulence)
|
||||
info.biomeValue = biomeValue
|
||||
info.biomeSurface = biomeSurface
|
||||
info.biomeFill = biomeFill
|
||||
local value = biomeValue * info.weight
|
||||
averageValue = averageValue + value
|
||||
--[[if findChoiceMaterial < 0 and findChoiceMaterial + weight >= 0 then
|
||||
choiceMaterial = biomeMaterial
|
||||
end
|
||||
findChoiceMaterial = findChoiceMaterial + weight]]
|
||||
end
|
||||
for biome,info in pairs(weightPoints) do
|
||||
local value = findBiomeTransitionValue(biome,info.weight,info.biomeValue,averageValue)
|
||||
if value > choiceValue then
|
||||
choiceValue = value
|
||||
choiceSurface = info.biomeSurface
|
||||
choiceFill = info.biomeFill
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local preCaveComp = verticalGradient*.5 + choiceValue*.5
|
||||
|
||||
local surface = preCaveComp > .5-surfaceThickness and preCaveComp < .5+surfaceThickness
|
||||
|
||||
if generateCaves --user wants caves
|
||||
and (not biomeNoCave or verticalGradient > .65) --biome allows caves or deep enough
|
||||
and not (surface and (1-verticalGradient) < waterLevel+.005) --caves only breach surface above waterlevel
|
||||
and not (surface and (1-verticalGradient) > waterLevel+.58) then --caves don't go too high so that they don't cut up mountain tops
|
||||
local ridged2 = ridgedFilter(getPerlin(x,y,z,4,30))
|
||||
local caves2 = thresholdFilter(ridged2,.84,.01)
|
||||
local ridged3 = ridgedFilter(getPerlin(x,y,z,5,30))
|
||||
local caves3 = thresholdFilter(ridged3,.84,.01)
|
||||
local ridged4 = ridgedFilter(getPerlin(x,y,z,6,30))
|
||||
local caves4 = thresholdFilter(ridged4,.84,.01)
|
||||
local caveOpenings = (surface and 1 or 0) * thresholdFilter(getPerlin(x,0,z,143,62),.35,0) --.45
|
||||
caves = caves2 * caves3 * caves4 - caveOpenings
|
||||
caves = caves < 0 and 0 or caves > 1 and 1 or caves
|
||||
end
|
||||
|
||||
local comp = preCaveComp - caves
|
||||
|
||||
local smoothedResult = thresholdFilter(comp,.5,smoothScale)
|
||||
|
||||
---below water level -above surface -no terrain
|
||||
if 1-verticalGradient < waterLevel and preCaveComp <= .5 and smoothedResult <= 0 then
|
||||
smoothedResult = 1
|
||||
choiceSurface = water
|
||||
choiceFill = water
|
||||
surface = true
|
||||
end
|
||||
|
||||
oMapY[z] = (y == 1 and 1) or smoothedResult
|
||||
mMapY[z] = (y == 1 and lava) or (smoothedResult <= 0 and air) or (surface and choiceSurface) or choiceFill
|
||||
end
|
||||
end
|
||||
|
||||
-- local regionStart = Vector3.new(mapWidth*-2+(x-1)*4,mapHeight*-2,mapWidth*-2)
|
||||
-- local regionEnd = Vector3.new(mapWidth*-2+x*4,mapHeight*2,mapWidth*2)
|
||||
-- local mapRegion = Region3.new(regionStart, regionEnd)
|
||||
-- terrain:WriteVoxels(mapRegion, 4, {mMapX}, {oMapX})
|
||||
end
|
||||
end
|
||||
|
||||
bench.runCode(generate, "voxelgen")
|
|
@ -91,6 +91,8 @@ TEST_CASE_FIXTURE(DocumentationSymbolFixture, "class_method")
|
|||
|
||||
TEST_CASE_FIXTURE(DocumentationSymbolFixture, "overloaded_class_method")
|
||||
{
|
||||
ScopedFastFlag luauCheckOverloadedDocSymbol{"LuauCheckOverloadedDocSymbol", true};
|
||||
|
||||
loadDefinition(R"(
|
||||
declare class Foo
|
||||
function bar(self, x: string): number
|
||||
|
@ -125,6 +127,8 @@ TEST_CASE_FIXTURE(DocumentationSymbolFixture, "table_function_prop")
|
|||
|
||||
TEST_CASE_FIXTURE(DocumentationSymbolFixture, "table_overloaded_function_prop")
|
||||
{
|
||||
ScopedFastFlag luauCheckOverloadedDocSymbol{"LuauCheckOverloadedDocSymbol", true};
|
||||
|
||||
loadDefinition(R"(
|
||||
declare Foo: {
|
||||
new: ((number) -> string) & ((string) -> number)
|
||||
|
|
|
@ -506,6 +506,15 @@ std::optional<TypeId> linearSearchForBinding(Scope* scope, const char* name)
|
|||
return std::nullopt;
|
||||
}
|
||||
|
||||
void registerNotType(Fixture& fixture, TypeArena& arena)
|
||||
{
|
||||
TypeId t = arena.addType(GenericTypeVar{"T"});
|
||||
GenericTypeDefinition genericT{t};
|
||||
|
||||
ScopePtr moduleScope = fixture.frontend.getGlobalScope();
|
||||
moduleScope->exportedTypeBindings["Not"] = TypeFun{{genericT}, arena.addType(NegationTypeVar{t})};
|
||||
}
|
||||
|
||||
void dump(const std::vector<Constraint>& constraints)
|
||||
{
|
||||
ToStringOptions opts;
|
||||
|
|
|
@ -186,6 +186,8 @@ std::optional<TypeId> lookupName(ScopePtr scope, const std::string& name); // Wa
|
|||
|
||||
std::optional<TypeId> linearSearchForBinding(Scope* scope, const char* name);
|
||||
|
||||
void registerNotType(Fixture& fixture, TypeArena& arena);
|
||||
|
||||
} // namespace Luau
|
||||
|
||||
#define LUAU_REQUIRE_ERRORS(result) \
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||
LUAU_FASTFLAG(LuauIceExceptionInheritanceChange);
|
||||
|
||||
TEST_SUITE_BEGIN("ModuleTests");
|
||||
|
||||
|
@ -278,7 +279,14 @@ TEST_CASE_FIXTURE(Fixture, "clone_recursion_limit")
|
|||
TypeArena dest;
|
||||
CloneState cloneState;
|
||||
|
||||
CHECK_THROWS_AS(clone(table, dest, cloneState), RecursionLimitException);
|
||||
if (FFlag::LuauIceExceptionInheritanceChange)
|
||||
{
|
||||
CHECK_THROWS_AS(clone(table, dest, cloneState), RecursionLimitException);
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_THROWS_AS(clone(table, dest, cloneState), RecursionLimitException_DEPRECATED);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "any_persistance_does_not_leak")
|
||||
|
|
|
@ -10,13 +10,16 @@
|
|||
|
||||
using namespace Luau;
|
||||
|
||||
struct NormalizeFixture : Fixture
|
||||
namespace
|
||||
{
|
||||
struct IsSubtypeFixture : Fixture
|
||||
{
|
||||
bool isSubtype(TypeId a, TypeId b)
|
||||
{
|
||||
return ::Luau::isSubtype(a, b, NotNull{getMainModule()->getModuleScope().get()}, singletonTypes, ice);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void createSomeClasses(Frontend& frontend)
|
||||
{
|
||||
|
@ -55,7 +58,7 @@ void createSomeClasses(Frontend& frontend)
|
|||
|
||||
TEST_SUITE_BEGIN("isSubtype");
|
||||
|
||||
TEST_CASE_FIXTURE(NormalizeFixture, "primitives")
|
||||
TEST_CASE_FIXTURE(IsSubtypeFixture, "primitives")
|
||||
{
|
||||
check(R"(
|
||||
local a = 41
|
||||
|
@ -75,7 +78,7 @@ TEST_CASE_FIXTURE(NormalizeFixture, "primitives")
|
|||
CHECK(!isSubtype(d, a));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NormalizeFixture, "functions")
|
||||
TEST_CASE_FIXTURE(IsSubtypeFixture, "functions")
|
||||
{
|
||||
check(R"(
|
||||
function a(x: number): number return x end
|
||||
|
@ -96,7 +99,7 @@ TEST_CASE_FIXTURE(NormalizeFixture, "functions")
|
|||
CHECK(isSubtype(a, d));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NormalizeFixture, "functions_and_any")
|
||||
TEST_CASE_FIXTURE(IsSubtypeFixture, "functions_and_any")
|
||||
{
|
||||
check(R"(
|
||||
function a(n: number) return "string" end
|
||||
|
@ -114,7 +117,7 @@ TEST_CASE_FIXTURE(NormalizeFixture, "functions_and_any")
|
|||
CHECK(!isSubtype(a, b));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NormalizeFixture, "variadic_functions_with_no_head")
|
||||
TEST_CASE_FIXTURE(IsSubtypeFixture, "variadic_functions_with_no_head")
|
||||
{
|
||||
check(R"(
|
||||
local a: (...number) -> ()
|
||||
|
@ -129,7 +132,7 @@ TEST_CASE_FIXTURE(NormalizeFixture, "variadic_functions_with_no_head")
|
|||
}
|
||||
|
||||
#if 0
|
||||
TEST_CASE_FIXTURE(NormalizeFixture, "variadic_function_with_head")
|
||||
TEST_CASE_FIXTURE(IsSubtypeFixture, "variadic_function_with_head")
|
||||
{
|
||||
check(R"(
|
||||
local a: (...number) -> ()
|
||||
|
@ -144,7 +147,7 @@ TEST_CASE_FIXTURE(NormalizeFixture, "variadic_function_with_head")
|
|||
}
|
||||
#endif
|
||||
|
||||
TEST_CASE_FIXTURE(NormalizeFixture, "union")
|
||||
TEST_CASE_FIXTURE(IsSubtypeFixture, "union")
|
||||
{
|
||||
check(R"(
|
||||
local a: number | string
|
||||
|
@ -171,7 +174,7 @@ TEST_CASE_FIXTURE(NormalizeFixture, "union")
|
|||
CHECK(!isSubtype(d, b));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NormalizeFixture, "table_with_union_prop")
|
||||
TEST_CASE_FIXTURE(IsSubtypeFixture, "table_with_union_prop")
|
||||
{
|
||||
check(R"(
|
||||
local a: {x: number}
|
||||
|
@ -185,7 +188,7 @@ TEST_CASE_FIXTURE(NormalizeFixture, "table_with_union_prop")
|
|||
CHECK(!isSubtype(b, a));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NormalizeFixture, "table_with_any_prop")
|
||||
TEST_CASE_FIXTURE(IsSubtypeFixture, "table_with_any_prop")
|
||||
{
|
||||
check(R"(
|
||||
local a: {x: number}
|
||||
|
@ -199,7 +202,7 @@ TEST_CASE_FIXTURE(NormalizeFixture, "table_with_any_prop")
|
|||
CHECK(!isSubtype(b, a));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NormalizeFixture, "intersection")
|
||||
TEST_CASE_FIXTURE(IsSubtypeFixture, "intersection")
|
||||
{
|
||||
ScopedFastFlag sffs[]{
|
||||
{"LuauSubtypeNormalizer", true},
|
||||
|
@ -229,7 +232,7 @@ TEST_CASE_FIXTURE(NormalizeFixture, "intersection")
|
|||
CHECK(isSubtype(a, d));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NormalizeFixture, "union_and_intersection")
|
||||
TEST_CASE_FIXTURE(IsSubtypeFixture, "union_and_intersection")
|
||||
{
|
||||
check(R"(
|
||||
local a: number & string
|
||||
|
@ -243,7 +246,7 @@ TEST_CASE_FIXTURE(NormalizeFixture, "union_and_intersection")
|
|||
CHECK(isSubtype(a, b));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NormalizeFixture, "tables")
|
||||
TEST_CASE_FIXTURE(IsSubtypeFixture, "tables")
|
||||
{
|
||||
check(R"(
|
||||
local a: {x: number}
|
||||
|
@ -271,7 +274,7 @@ TEST_CASE_FIXTURE(NormalizeFixture, "tables")
|
|||
}
|
||||
|
||||
#if 0
|
||||
TEST_CASE_FIXTURE(NormalizeFixture, "table_indexers_are_invariant")
|
||||
TEST_CASE_FIXTURE(IsSubtypeFixture, "table_indexers_are_invariant")
|
||||
{
|
||||
check(R"(
|
||||
local a: {[string]: number}
|
||||
|
@ -290,7 +293,7 @@ TEST_CASE_FIXTURE(NormalizeFixture, "table_indexers_are_invariant")
|
|||
CHECK(isSubtype(a, c));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NormalizeFixture, "mismatched_indexers")
|
||||
TEST_CASE_FIXTURE(IsSubtypeFixture, "mismatched_indexers")
|
||||
{
|
||||
check(R"(
|
||||
local a: {x: number}
|
||||
|
@ -309,7 +312,7 @@ TEST_CASE_FIXTURE(NormalizeFixture, "mismatched_indexers")
|
|||
CHECK(isSubtype(b, c));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NormalizeFixture, "cyclic_table")
|
||||
TEST_CASE_FIXTURE(IsSubtypeFixture, "cyclic_table")
|
||||
{
|
||||
check(R"(
|
||||
type A = {method: (A) -> ()}
|
||||
|
@ -348,7 +351,7 @@ TEST_CASE_FIXTURE(NormalizeFixture, "cyclic_table")
|
|||
}
|
||||
#endif
|
||||
|
||||
TEST_CASE_FIXTURE(NormalizeFixture, "classes")
|
||||
TEST_CASE_FIXTURE(IsSubtypeFixture, "classes")
|
||||
{
|
||||
createSomeClasses(frontend);
|
||||
|
||||
|
@ -365,7 +368,7 @@ TEST_CASE_FIXTURE(NormalizeFixture, "classes")
|
|||
}
|
||||
|
||||
#if 0
|
||||
TEST_CASE_FIXTURE(NormalizeFixture, "metatable" * doctest::expected_failures{1})
|
||||
TEST_CASE_FIXTURE(IsSubtypeFixture, "metatable" * doctest::expected_failures{1})
|
||||
{
|
||||
check(R"(
|
||||
local T = {}
|
||||
|
@ -389,8 +392,112 @@ TEST_CASE_FIXTURE(NormalizeFixture, "metatable" * doctest::expected_failures{1})
|
|||
|
||||
TEST_SUITE_END();
|
||||
|
||||
struct NormalizeFixture : Fixture
|
||||
{
|
||||
ScopedFastFlag sff{"LuauNegatedStringSingletons", true};
|
||||
|
||||
TypeArena arena;
|
||||
InternalErrorReporter iceHandler;
|
||||
UnifierSharedState unifierState{&iceHandler};
|
||||
Normalizer normalizer{&arena, singletonTypes, NotNull{&unifierState}};
|
||||
|
||||
NormalizeFixture()
|
||||
{
|
||||
registerNotType(*this, arena);
|
||||
}
|
||||
|
||||
TypeId normal(const std::string& annotation)
|
||||
{
|
||||
CheckResult result = check("type _Res = " + annotation);
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
std::optional<TypeId> ty = lookupType("_Res");
|
||||
REQUIRE(ty);
|
||||
const NormalizedType* norm = normalizer.normalize(*ty);
|
||||
REQUIRE(norm);
|
||||
return normalizer.typeFromNormal(*norm);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_SUITE_BEGIN("Normalize");
|
||||
|
||||
TEST_CASE_FIXTURE(NormalizeFixture, "negate_string")
|
||||
{
|
||||
CHECK("number" == toString(normal(R"(
|
||||
(number | string) & Not<string>
|
||||
)")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NormalizeFixture, "negate_string_from_cofinite_string_intersection")
|
||||
{
|
||||
CHECK("number" == toString(normal(R"(
|
||||
(number | (string & Not<"hello"> & Not<"world">)) & Not<string>
|
||||
)")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NormalizeFixture, "no_op_negation_is_dropped")
|
||||
{
|
||||
CHECK("number" == toString(normal(R"(
|
||||
number & Not<string>
|
||||
)")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NormalizeFixture, "union_of_negation")
|
||||
{
|
||||
CHECK("string" == toString(normal(R"(
|
||||
(string & Not<"hello">) | "hello"
|
||||
)")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NormalizeFixture, "intersect_truthy")
|
||||
{
|
||||
CHECK("number | string | true" == toString(normal(R"(
|
||||
(string | number | boolean | nil) & Not<false | nil>
|
||||
)")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NormalizeFixture, "intersect_truthy_expressed_as_intersection")
|
||||
{
|
||||
CHECK("number | string | true" == toString(normal(R"(
|
||||
(string | number | boolean | nil) & Not<false> & Not<nil>
|
||||
)")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NormalizeFixture, "union_of_union")
|
||||
{
|
||||
CHECK(R"("alpha" | "beta" | "gamma")" == toString(normal(R"(
|
||||
("alpha" | "beta") | "gamma"
|
||||
)")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NormalizeFixture, "union_of_negations")
|
||||
{
|
||||
CHECK(R"(string & ~"world")" == toString(normal(R"(
|
||||
(string & Not<"hello"> & Not<"world">) | (string & Not<"goodbye"> & Not<"world">)
|
||||
)")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NormalizeFixture, "negate_boolean")
|
||||
{
|
||||
CHECK("true" == toString(normal(R"(
|
||||
boolean & Not<false>
|
||||
)")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NormalizeFixture, "negate_boolean_2")
|
||||
{
|
||||
CHECK("never" == toString(normal(R"(
|
||||
true & Not<true>
|
||||
)")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NormalizeFixture, "bare_negation")
|
||||
{
|
||||
// TODO: We don't yet have a way to say number | string | thread | nil | Class | Table | Function
|
||||
CHECK("(number | string | thread)?" == toString(normal(R"(
|
||||
Not<boolean>
|
||||
)")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "higher_order_function")
|
||||
{
|
||||
check(R"(
|
||||
|
|
|
@ -1736,8 +1736,6 @@ TEST_CASE_FIXTURE(Fixture, "parse_error_type_annotation")
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "parse_error_missing_type_annotation")
|
||||
{
|
||||
ScopedFastFlag LuauTypeAnnotationLocationChange{"LuauTypeAnnotationLocationChange", true};
|
||||
|
||||
{
|
||||
ParseResult result = tryParse("local x:");
|
||||
CHECK(result.errors.size() == 1);
|
||||
|
|
|
@ -512,9 +512,11 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "toStringDetailed2")
|
|||
|
||||
TableTypeVar* tMeta5 = getMutable<TableTypeVar>(tMeta4->props["__index"].type);
|
||||
REQUIRE(tMeta5);
|
||||
REQUIRE(tMeta5->props.count("one") > 0);
|
||||
|
||||
TableTypeVar* tMeta6 = getMutable<TableTypeVar>(tMeta3->table);
|
||||
REQUIRE(tMeta6);
|
||||
REQUIRE(tMeta6->props.count("two") > 0);
|
||||
|
||||
ToStringResult oneResult = toStringDetailed(tMeta5->props["one"].type, opts);
|
||||
if (!FFlag::LuauFixNameMaps)
|
||||
|
|
|
@ -913,4 +913,14 @@ TEST_CASE_FIXTURE(Fixture, "it_is_ok_to_shadow_user_defined_alias")
|
|||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "cannot_create_cyclic_type_with_unknown_module")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
type AAA = B.AAA
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK(toString(result.errors[0]) == "Unknown type 'B.AAA'");
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
#include "doctest.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauIceExceptionInheritanceChange)
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
TEST_SUITE_BEGIN("AnnotationTests");
|
||||
|
@ -664,8 +666,8 @@ TEST_CASE_FIXTURE(Fixture, "luau_ice_triggers_an_ice_exception_with_flag")
|
|||
AssertionCatcher ac;
|
||||
|
||||
CHECK_THROWS_AS(check(R"(
|
||||
local a: _luau_ice = 55
|
||||
)"),
|
||||
local a: _luau_ice = 55
|
||||
)"),
|
||||
InternalCompilerError);
|
||||
|
||||
LUAU_ASSERT(1 == AssertionCatcher::tripped);
|
||||
|
@ -682,8 +684,8 @@ TEST_CASE_FIXTURE(Fixture, "luau_ice_triggers_an_ice_exception_with_flag_handler
|
|||
};
|
||||
|
||||
CHECK_THROWS_AS(check(R"(
|
||||
local a: _luau_ice = 55
|
||||
)"),
|
||||
local a: _luau_ice = 55
|
||||
)"),
|
||||
InternalCompilerError);
|
||||
|
||||
CHECK_EQ(true, caught);
|
||||
|
|
|
@ -348,8 +348,6 @@ TEST_CASE_FIXTURE(Fixture, "prop_access_on_any_with_other_options")
|
|||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "union_of_types_regression_test")
|
||||
{
|
||||
ScopedFastFlag LuauUnionOfTypesFollow{"LuauUnionOfTypesFollow", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
--!strict
|
||||
local stat
|
||||
|
|
|
@ -309,6 +309,23 @@ TEST_CASE_FIXTURE(Fixture, "definitions_documentation_symbols")
|
|||
CHECK_EQ(yTtv->props["x"].documentationSymbol, "@test/global/y.x");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "definitions_symbols_are_generated_for_recursively_referenced_types")
|
||||
{
|
||||
ScopedFastFlag LuauPersistTypesAfterGeneratingDocSyms("LuauPersistTypesAfterGeneratingDocSyms", true);
|
||||
|
||||
loadDefinition(R"(
|
||||
declare class MyClass
|
||||
function myMethod(self)
|
||||
end
|
||||
|
||||
declare function myFunc(): MyClass
|
||||
)");
|
||||
|
||||
std::optional<TypeFun> myClassTy = typeChecker.globalScope->lookupType("MyClass");
|
||||
REQUIRE(bool(myClassTy));
|
||||
CHECK_EQ(myClassTy->type->documentationSymbol, "@test/globaltype/MyClass");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "documentation_symbols_dont_attach_to_persistent_types")
|
||||
{
|
||||
loadDefinition(R"(
|
||||
|
|
|
@ -229,6 +229,48 @@ TEST_CASE_FIXTURE(Fixture, "too_many_arguments")
|
|||
CHECK_EQ(0, acm->actual);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "too_many_arguments_error_location")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauArgMismatchReportFunctionLocation", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
--!strict
|
||||
|
||||
function myfunction(a: number, b:number) end
|
||||
myfunction(1)
|
||||
|
||||
function getmyfunction()
|
||||
return myfunction
|
||||
end
|
||||
getmyfunction()()
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||
|
||||
{
|
||||
TypeError err = result.errors[0];
|
||||
|
||||
// Ensure the location matches the location of the function identifier
|
||||
CHECK_EQ(err.location, Location(Position(4, 8), Position(4, 18)));
|
||||
|
||||
auto acm = get<CountMismatch>(err);
|
||||
REQUIRE(acm);
|
||||
CHECK_EQ(2, acm->expected);
|
||||
CHECK_EQ(1, acm->actual);
|
||||
}
|
||||
{
|
||||
TypeError err = result.errors[1];
|
||||
|
||||
// Ensure the location matches the location of the expression returning the function
|
||||
CHECK_EQ(err.location, Location(Position(9, 8), Position(9, 23)));
|
||||
|
||||
auto acm = get<CountMismatch>(err);
|
||||
REQUIRE(acm);
|
||||
CHECK_EQ(2, acm->expected);
|
||||
CHECK_EQ(0, acm->actual);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "recursive_function")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
|
@ -1659,17 +1701,20 @@ foo3()
|
|||
string.find()
|
||||
|
||||
local t = {}
|
||||
function t.foo(x: number, y: string?, ...: any) end
|
||||
function t.foo(x: number, y: string?, ...: any) return 1 end
|
||||
function t:bar(x: number, y: string?) end
|
||||
t.foo()
|
||||
|
||||
t:bar()
|
||||
|
||||
local u = { a = t }
|
||||
local u = { a = t, b = function() return t end }
|
||||
u.a.foo()
|
||||
local x = (u.a).foo()
|
||||
|
||||
u.b().foo()
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(7, result);
|
||||
LUAU_REQUIRE_ERROR_COUNT(9, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Argument count mismatch. Function 'foo1' expects 1 argument, but none are specified");
|
||||
CHECK_EQ(toString(result.errors[1]), "Argument count mismatch. Function 'foo2' expects 1 to 2 arguments, but none are specified");
|
||||
CHECK_EQ(toString(result.errors[2]), "Argument count mismatch. Function 'foo3' expects 1 to 3 arguments, but none are specified");
|
||||
|
@ -1677,6 +1722,8 @@ u.a.foo()
|
|||
CHECK_EQ(toString(result.errors[4]), "Argument count mismatch. Function 't.foo' expects at least 1 argument, but none are specified");
|
||||
CHECK_EQ(toString(result.errors[5]), "Argument count mismatch. Function 't.bar' expects 2 to 3 arguments, but only 1 is specified");
|
||||
CHECK_EQ(toString(result.errors[6]), "Argument count mismatch. Function 'u.a.foo' expects at least 1 argument, but none are specified");
|
||||
CHECK_EQ(toString(result.errors[7]), "Argument count mismatch. Function 'u.a.foo' expects at least 1 argument, but none are specified");
|
||||
CHECK_EQ(toString(result.errors[8]), "Argument count mismatch. Function expects at least 1 argument, but none are specified");
|
||||
}
|
||||
|
||||
// This might be surprising, but since 'any' became optional, unannotated functions in non-strict 'expect' 0 arguments
|
||||
|
|
|
@ -251,23 +251,7 @@ end
|
|||
return m
|
||||
)");
|
||||
|
||||
if (FFlag::LuauInstantiateInSubtyping)
|
||||
{
|
||||
// though this didn't error before the flag, it seems as though it should error since fields of a table are invariant.
|
||||
// the user's intent would likely be that these "method" fields would be read-only, but without an annotation, accepting this should be
|
||||
// unsound.
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
CHECK_EQ(R"(Type 'n' could not be converted into 't1 where t1 = {- Clone: (t1) -> (a...) -}'
|
||||
caused by:
|
||||
Property 'Clone' is not compatible. Type '<a>(a) -> ()' could not be converted into 't1 where t1 = ({- Clone: t1 -}) -> (a...)'; different number of generic type parameters)",
|
||||
toString(result.errors[0]));
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "custom_require_global")
|
||||
|
|
52
tests/TypeInfer.negations.test.cpp
Normal file
52
tests/TypeInfer.negations.test.cpp
Normal file
|
@ -0,0 +1,52 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
|
||||
#include "Fixture.h"
|
||||
|
||||
#include "doctest.h"
|
||||
#include "Luau/Common.h"
|
||||
#include "ScopedFlags.h"
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
namespace
|
||||
{
|
||||
struct NegationFixture : Fixture
|
||||
{
|
||||
TypeArena arena;
|
||||
ScopedFastFlag sff[2] {
|
||||
{"LuauNegatedStringSingletons", true},
|
||||
{"LuauSubtypeNormalizer", true},
|
||||
};
|
||||
|
||||
NegationFixture()
|
||||
{
|
||||
registerNotType(*this, arena);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
TEST_SUITE_BEGIN("Negations");
|
||||
|
||||
TEST_CASE_FIXTURE(NegationFixture, "negated_string_is_a_subtype_of_string")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
function foo(arg: string) end
|
||||
local a: string & Not<"Hello">
|
||||
foo(a)
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NegationFixture, "string_is_not_a_subtype_of_negated_string")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
function foo(arg: string & Not<"hello">) end
|
||||
local a: string
|
||||
foo(a)
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
|
@ -434,16 +434,17 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "typecheck_unary_minus")
|
|||
{
|
||||
CheckResult result = check(R"(
|
||||
--!strict
|
||||
local foo = {
|
||||
value = 10
|
||||
}
|
||||
local foo
|
||||
local mt = {}
|
||||
setmetatable(foo, mt)
|
||||
|
||||
mt.__unm = function(val: typeof(foo)): string
|
||||
return val.value .. "test"
|
||||
return tostring(val.value) .. "test"
|
||||
end
|
||||
|
||||
foo = setmetatable({
|
||||
value = 10
|
||||
}, mt)
|
||||
|
||||
local a = -foo
|
||||
|
||||
local b = 1+-1
|
||||
|
@ -459,25 +460,32 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "typecheck_unary_minus")
|
|||
CHECK_EQ("string", toString(requireType("a")));
|
||||
CHECK_EQ("number", toString(requireType("b")));
|
||||
|
||||
GenericError* gen = get<GenericError>(result.errors[0]);
|
||||
REQUIRE_EQ(gen->message, "Unary operator '-' not supported by type 'bar'");
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK(toString(result.errors[0]) == "Type '{ value: number }' could not be converted into 'number'");
|
||||
}
|
||||
else
|
||||
{
|
||||
GenericError* gen = get<GenericError>(result.errors[0]);
|
||||
REQUIRE(gen);
|
||||
REQUIRE_EQ(gen->message, "Unary operator '-' not supported by type 'bar'");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "typecheck_unary_minus_error")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
--!strict
|
||||
local foo = {
|
||||
value = 10
|
||||
}
|
||||
|
||||
local mt = {}
|
||||
setmetatable(foo, mt)
|
||||
|
||||
mt.__unm = function(val: boolean): string
|
||||
return "test"
|
||||
end
|
||||
|
||||
local foo = setmetatable({
|
||||
value = 10
|
||||
}, mt)
|
||||
|
||||
local a = -foo
|
||||
)");
|
||||
|
||||
|
@ -494,16 +502,16 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "typecheck_unary_len_error")
|
|||
{
|
||||
CheckResult result = check(R"(
|
||||
--!strict
|
||||
local foo = {
|
||||
value = 10
|
||||
}
|
||||
local mt = {}
|
||||
setmetatable(foo, mt)
|
||||
|
||||
mt.__len = function(val: any): string
|
||||
mt.__len = function(val): string
|
||||
return "test"
|
||||
end
|
||||
|
||||
local foo = setmetatable({
|
||||
value = 10,
|
||||
}, mt)
|
||||
|
||||
local a = #foo
|
||||
)");
|
||||
|
||||
|
|
|
@ -624,15 +624,18 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_insert_with_a_singleton_argument")
|
|||
CHECK_EQ("{string | string}", toString(requireType("t")));
|
||||
}
|
||||
|
||||
struct NormalizeFixture : Fixture
|
||||
namespace
|
||||
{
|
||||
struct IsSubtypeFixture : Fixture
|
||||
{
|
||||
bool isSubtype(TypeId a, TypeId b)
|
||||
{
|
||||
return ::Luau::isSubtype(a, b, NotNull{getMainModule()->getModuleScope().get()}, singletonTypes, ice);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NormalizeFixture, "intersection_of_functions_of_different_arities")
|
||||
TEST_CASE_FIXTURE(IsSubtypeFixture, "intersection_of_functions_of_different_arities")
|
||||
{
|
||||
check(R"(
|
||||
type A = (any) -> ()
|
||||
|
@ -653,7 +656,7 @@ TEST_CASE_FIXTURE(NormalizeFixture, "intersection_of_functions_of_different_arit
|
|||
CHECK("((any) -> ()) & ((any, any) -> ())" == toString(requireType("t")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NormalizeFixture, "functions_with_mismatching_arity")
|
||||
TEST_CASE_FIXTURE(IsSubtypeFixture, "functions_with_mismatching_arity")
|
||||
{
|
||||
check(R"(
|
||||
local a: (number) -> ()
|
||||
|
@ -676,7 +679,7 @@ TEST_CASE_FIXTURE(NormalizeFixture, "functions_with_mismatching_arity")
|
|||
CHECK(!isSubtype(b, c));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NormalizeFixture, "functions_with_mismatching_arity_but_optional_parameters")
|
||||
TEST_CASE_FIXTURE(IsSubtypeFixture, "functions_with_mismatching_arity_but_optional_parameters")
|
||||
{
|
||||
/*
|
||||
* (T0..TN) <: (T0..TN, A?)
|
||||
|
@ -736,7 +739,7 @@ TEST_CASE_FIXTURE(NormalizeFixture, "functions_with_mismatching_arity_but_option
|
|||
// CHECK(!isSubtype(b, c));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NormalizeFixture, "functions_with_mismatching_arity_but_any_is_an_optional_param")
|
||||
TEST_CASE_FIXTURE(IsSubtypeFixture, "functions_with_mismatching_arity_but_any_is_an_optional_param")
|
||||
{
|
||||
check(R"(
|
||||
local a: (number?) -> ()
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/BuiltinDefinitions.h"
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/Frontend.h"
|
||||
#include "Luau/ToString.h"
|
||||
#include "Luau/TypeInfer.h"
|
||||
#include "Luau/TypeVar.h"
|
||||
|
||||
|
@ -14,6 +17,7 @@ using namespace Luau;
|
|||
LUAU_FASTFLAG(LuauLowerBoundsCalculation);
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||
LUAU_FASTFLAG(LuauSpecialTypesAsterisked)
|
||||
|
||||
TEST_SUITE_BEGIN("TableTests");
|
||||
|
||||
|
@ -1957,7 +1961,11 @@ TEST_CASE_FIXTURE(Fixture, "invariant_table_properties_means_instantiating_table
|
|||
local c : string = t.m("hi")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
// TODO: test behavior is wrong with LuauInstantiateInSubtyping until we can re-enable the covariant requirement for instantiation in subtyping
|
||||
if (FFlag::LuauInstantiateInSubtyping)
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
else
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "table_insert_should_cope_with_optional_properties_in_nonstrict")
|
||||
|
@ -3262,11 +3270,13 @@ TEST_CASE_FIXTURE(Fixture, "invariant_table_properties_means_instantiating_table
|
|||
local c : string = t.m("hi")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
CHECK_EQ(toString(result.errors[0]), R"(Type 't' could not be converted into '{| m: (number) -> number |}'
|
||||
caused by:
|
||||
Property 'm' is not compatible. Type '<a>(a) -> a' could not be converted into '(number) -> number'; different number of generic type parameters)");
|
||||
// this error message is not great since the underlying issue is that the context is invariant,
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
// TODO: test behavior is wrong until we can re-enable the covariant requirement for instantiation in subtyping
|
||||
// LUAU_REQUIRE_ERRORS(result);
|
||||
// CHECK_EQ(toString(result.errors[0]), R"(Type 't' could not be converted into '{| m: (number) -> number |}'
|
||||
// caused by:
|
||||
// Property 'm' is not compatible. Type '<a>(a) -> a' could not be converted into '(number) -> number'; different number of generic type parameters)");
|
||||
// // this error message is not great since the underlying issue is that the context is invariant,
|
||||
// and `(number) -> number` cannot be a subtype of `<a>(a) -> a`.
|
||||
}
|
||||
|
||||
|
@ -3292,4 +3302,43 @@ local g : ({ p : number, q : string }) -> ({ p : number, r : boolean }) = f
|
|||
CHECK_EQ("r", error->properties[0]);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "setmetatable_has_a_side_effect")
|
||||
{
|
||||
if (!FFlag::DebugLuauDeferredConstraintResolution)
|
||||
return;
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local mt = {
|
||||
__add = function(x, y)
|
||||
return 123
|
||||
end,
|
||||
}
|
||||
|
||||
local foo = {}
|
||||
setmetatable(foo, mt)
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK(toString(requireType("foo")) == "{ @metatable { __add: (a, b) -> number }, { } }");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "tables_should_be_fully_populated")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local t = {
|
||||
x = 5 :: NonexistingTypeWhichEndsUpReturningAnErrorType,
|
||||
y = 5
|
||||
}
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
ToStringOptions opts;
|
||||
opts.exhaustive = true;
|
||||
if (FFlag::LuauSpecialTypesAsterisked)
|
||||
CHECK_EQ("{ x: *error-type*, y: number }", toString(requireType("t"), opts));
|
||||
else
|
||||
CHECK_EQ("{ x: <error-type>, y: number }", toString(requireType("t"), opts));
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -22,7 +22,14 @@ TEST_CASE_FIXTURE(Fixture, "throw_when_limit_is_exceeded")
|
|||
|
||||
TypeId tType = requireType("t");
|
||||
|
||||
CHECK_THROWS_AS(toString(tType), RecursionLimitException);
|
||||
if (FFlag::LuauIceExceptionInheritanceChange)
|
||||
{
|
||||
CHECK_THROWS_AS(toString(tType), RecursionLimitException);
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_THROWS_AS(toString(tType), RecursionLimitException_DEPRECATED);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "dont_throw_when_limit_is_high_enough")
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
AnnotationTests.builtin_types_are_not_exported
|
||||
AnnotationTests.corecursive_types_error_on_tight_loop
|
||||
AnnotationTests.duplicate_type_param_name
|
||||
AnnotationTests.for_loop_counter_annotation_is_checked
|
||||
AnnotationTests.generic_aliases_are_cloned_properly
|
||||
AnnotationTests.instantiation_clone_has_to_follow
|
||||
AnnotationTests.luau_print_is_not_special_without_the_flag
|
||||
AnnotationTests.occurs_check_on_cyclic_intersection_typevar
|
||||
AnnotationTests.occurs_check_on_cyclic_union_typevar
|
||||
AnnotationTests.too_many_type_params
|
||||
AnnotationTests.two_type_params
|
||||
AnnotationTests.use_type_required_from_another_file
|
||||
AnnotationTests.unknown_type_reference_generates_error
|
||||
AstQuery.last_argument_function_call_type
|
||||
AstQuery::getDocumentationSymbolAtPosition.overloaded_fn
|
||||
AutocompleteTest.autocomplete_first_function_arg_expected_type
|
||||
|
@ -86,7 +86,6 @@ BuiltinTests.table_pack
|
|||
BuiltinTests.table_pack_reduce
|
||||
BuiltinTests.table_pack_variadic
|
||||
BuiltinTests.tonumber_returns_optional_number_type
|
||||
BuiltinTests.tonumber_returns_optional_number_type2
|
||||
DefinitionTests.class_definition_overload_metamethods
|
||||
DefinitionTests.class_definition_string_props
|
||||
DefinitionTests.declaring_generic_functions
|
||||
|
@ -96,7 +95,6 @@ FrontendTest.imported_table_modification_2
|
|||
FrontendTest.it_should_be_safe_to_stringify_errors_when_full_type_graph_is_discarded
|
||||
FrontendTest.nocheck_cycle_used_by_checked
|
||||
FrontendTest.reexport_cyclic_type
|
||||
FrontendTest.reexport_type_alias
|
||||
FrontendTest.trace_requires_in_nonstrict_mode
|
||||
GenericsTests.apply_type_function_nested_generics1
|
||||
GenericsTests.apply_type_function_nested_generics2
|
||||
|
@ -105,7 +103,6 @@ GenericsTests.calling_self_generic_methods
|
|||
GenericsTests.check_generic_typepack_function
|
||||
GenericsTests.check_mutual_generic_functions
|
||||
GenericsTests.correctly_instantiate_polymorphic_member_functions
|
||||
GenericsTests.do_not_always_instantiate_generic_intersection_types
|
||||
GenericsTests.do_not_infer_generic_functions
|
||||
GenericsTests.duplicate_generic_type_packs
|
||||
GenericsTests.duplicate_generic_types
|
||||
|
@ -143,7 +140,6 @@ IntersectionTypes.table_write_sealed_indirect
|
|||
ModuleTests.any_persistance_does_not_leak
|
||||
ModuleTests.clone_self_property
|
||||
ModuleTests.deepClone_cyclic_table
|
||||
ModuleTests.do_not_clone_reexports
|
||||
NonstrictModeTests.for_in_iterator_variables_are_any
|
||||
NonstrictModeTests.function_parameters_are_any
|
||||
NonstrictModeTests.inconsistent_module_return_types_are_ok
|
||||
|
@ -158,7 +154,6 @@ NonstrictModeTests.parameters_having_type_any_are_optional
|
|||
NonstrictModeTests.table_dot_insert_and_recursive_calls
|
||||
NonstrictModeTests.table_props_are_any
|
||||
Normalize.cyclic_table_normalizes_sensibly
|
||||
Normalize.intersection_combine_on_bound_self
|
||||
ParseErrorRecovery.generic_type_list_recovery
|
||||
ParseErrorRecovery.recovery_of_parenthesized_expressions
|
||||
ParserTests.parse_nesting_based_end_detection_failsafe_earlier
|
||||
|
@ -249,7 +244,6 @@ TableTests.defining_a_self_method_for_a_builtin_sealed_table_must_fail
|
|||
TableTests.defining_a_self_method_for_a_local_sealed_table_must_fail
|
||||
TableTests.dont_crash_when_setmetatable_does_not_produce_a_metatabletypevar
|
||||
TableTests.dont_hang_when_trying_to_look_up_in_cyclic_metatable_index
|
||||
TableTests.dont_invalidate_the_properties_iterator_of_free_table_when_rolled_back
|
||||
TableTests.dont_leak_free_table_props
|
||||
TableTests.dont_quantify_table_that_belongs_to_outer_scope
|
||||
TableTests.dont_suggest_exact_match_keys
|
||||
|
@ -279,7 +273,6 @@ TableTests.inferring_crazy_table_should_also_be_quick
|
|||
TableTests.instantiate_table_cloning_3
|
||||
TableTests.invariant_table_properties_means_instantiating_tables_in_call_is_unsound
|
||||
TableTests.leaking_bad_metatable_errors
|
||||
TableTests.length_operator_union_errors
|
||||
TableTests.less_exponential_blowup_please
|
||||
TableTests.meta_add
|
||||
TableTests.meta_add_both_ways
|
||||
|
@ -347,9 +340,9 @@ TryUnifyTests.members_of_failed_typepack_unification_are_unified_with_errorType
|
|||
TryUnifyTests.result_of_failed_typepack_unification_is_constrained
|
||||
TryUnifyTests.typepack_unification_should_trim_free_tails
|
||||
TryUnifyTests.variadics_should_use_reversed_properly
|
||||
TypeAliases.cannot_create_cyclic_type_with_unknown_module
|
||||
TypeAliases.forward_declared_alias_is_not_clobbered_by_prior_unification_with_any
|
||||
TypeAliases.generic_param_remap
|
||||
TypeAliases.mismatched_generic_pack_type_param
|
||||
TypeAliases.mismatched_generic_type_param
|
||||
TypeAliases.mutually_recursive_types_restriction_not_ok_1
|
||||
TypeAliases.mutually_recursive_types_restriction_not_ok_2
|
||||
|
@ -363,7 +356,7 @@ TypeAliases.type_alias_fwd_declaration_is_precise
|
|||
TypeAliases.type_alias_local_mutation
|
||||
TypeAliases.type_alias_local_rename
|
||||
TypeAliases.type_alias_of_an_imported_recursive_generic_type
|
||||
TypeAliases.type_alias_of_an_imported_recursive_type
|
||||
TypeInfer.check_type_infer_recursion_count
|
||||
TypeInfer.checking_should_not_ice
|
||||
TypeInfer.cli_50041_committing_txnlog_in_apollo_client_error
|
||||
TypeInfer.dont_report_type_errors_within_an_AstExprError
|
||||
|
@ -394,6 +387,7 @@ TypeInferClasses.warn_when_prop_almost_matches
|
|||
TypeInferClasses.we_can_report_when_someone_is_trying_to_use_a_table_rather_than_a_class
|
||||
TypeInferFunctions.calling_function_with_anytypepack_doesnt_leak_free_types
|
||||
TypeInferFunctions.calling_function_with_incorrect_argument_type_yields_errors_spanning_argument
|
||||
TypeInferFunctions.cannot_hoist_interior_defns_into_signature
|
||||
TypeInferFunctions.dont_give_other_overloads_message_if_only_one_argument_matching_overload_exists
|
||||
TypeInferFunctions.dont_infer_parameter_types_for_functions_from_their_call_site
|
||||
TypeInferFunctions.duplicate_functions_with_different_signatures_not_allowed_in_nonstrict
|
||||
|
@ -439,12 +433,9 @@ TypeInferLoops.varlist_declared_by_for_in_loop_should_be_free
|
|||
TypeInferModules.bound_free_table_export_is_ok
|
||||
TypeInferModules.custom_require_global
|
||||
TypeInferModules.do_not_modify_imported_types
|
||||
TypeInferModules.do_not_modify_imported_types_2
|
||||
TypeInferModules.do_not_modify_imported_types_3
|
||||
TypeInferModules.module_type_conflict
|
||||
TypeInferModules.module_type_conflict_instantiated
|
||||
TypeInferModules.require_a_variadic_function
|
||||
TypeInferModules.require_types
|
||||
TypeInferModules.type_error_of_unknown_qualified_type
|
||||
TypeInferOOP.CheckMethodsOfSealed
|
||||
TypeInferOOP.dont_suggest_using_colon_rather_than_dot_if_another_overload_works
|
||||
|
@ -468,9 +459,6 @@ TypeInferOperators.produce_the_correct_error_message_when_comparing_a_table_with
|
|||
TypeInferOperators.refine_and_or
|
||||
TypeInferOperators.typecheck_overloaded_multiply_that_is_an_intersection
|
||||
TypeInferOperators.typecheck_overloaded_multiply_that_is_an_intersection_on_rhs
|
||||
TypeInferOperators.typecheck_unary_len_error
|
||||
TypeInferOperators.typecheck_unary_minus
|
||||
TypeInferOperators.typecheck_unary_minus_error
|
||||
TypeInferOperators.UnknownGlobalCompoundAssign
|
||||
TypeInferPrimitives.CheckMethodsOfNumber
|
||||
TypeInferPrimitives.singleton_types
|
||||
|
@ -489,6 +477,7 @@ TypeInferUnknownNever.math_operators_and_never
|
|||
TypeInferUnknownNever.type_packs_containing_never_is_itself_uninhabitable
|
||||
TypeInferUnknownNever.type_packs_containing_never_is_itself_uninhabitable2
|
||||
TypeInferUnknownNever.unary_minus_of_never
|
||||
TypePackTests.detect_cyclic_typepacks2
|
||||
TypePackTests.higher_order_function
|
||||
TypePackTests.pack_tail_unification_check
|
||||
TypePackTests.parenthesized_varargs_returns_any
|
||||
|
|
Loading…
Reference in a new issue