mirror of
https://github.com/luau-lang/luau.git
synced 2024-11-15 06:15:44 +08:00
Sync to upstream/release/646 (#1458)
# General Updates * Fix some cases where documentation symbols would not be available when mouseovering at certain positions in the code * Scaffolding to help embedders have more control over how `typeof(x)` refines types * Refinements to require-by-string semantics. See https://github.com/luau-lang/rfcs/pull/56 for details. * Fix for https://github.com/luau-lang/luau/issues/1405 # New Solver * Fix many crashes (thanks you for your bug reports!) * Type functions can now call each other * Type functions all evaluate in a single VM. This should improve typechecking performance and reduce memory use. * `export type function` is now forbidden and fails with a clear error message * Type functions that access locals in the surrounding environment are now properly a parse error * You can now use `:setindexer(types.never, types.never)` to delete an indexer from a table type. # Internal Contributors Co-authored-by: Aaron Weiss <aaronweiss@roblox.com> Co-authored-by: Hunter Goldstein <hgoldstein@roblox.com> Co-authored-by: Varun Saini <vsaini@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
This commit is contained in:
parent
02241b6d24
commit
543de6e939
@ -28,6 +28,7 @@ struct Scope;
|
||||
using ScopePtr = std::shared_ptr<Scope>;
|
||||
|
||||
struct DcrLogger;
|
||||
struct TypeFunctionRuntime;
|
||||
|
||||
struct Inference
|
||||
{
|
||||
@ -108,6 +109,8 @@ struct ConstraintGenerator
|
||||
|
||||
// Needed to be able to enable error-suppression preservation for immediate refinements.
|
||||
NotNull<Normalizer> normalizer;
|
||||
// Needed to register all available type functions for execution at later stages.
|
||||
NotNull<TypeFunctionRuntime> typeFunctionRuntime;
|
||||
// Needed to resolve modules to make 'require' import types properly.
|
||||
NotNull<ModuleResolver> moduleResolver;
|
||||
// Occasionally constraint generation needs to produce an ICE.
|
||||
@ -125,6 +128,7 @@ struct ConstraintGenerator
|
||||
ConstraintGenerator(
|
||||
ModulePtr module,
|
||||
NotNull<Normalizer> normalizer,
|
||||
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
|
||||
NotNull<ModuleResolver> moduleResolver,
|
||||
NotNull<BuiltinTypes> builtinTypes,
|
||||
NotNull<InternalErrorReporter> ice,
|
||||
@ -223,7 +227,10 @@ private:
|
||||
);
|
||||
void applyRefinements(const ScopePtr& scope, Location location, RefinementId refinement);
|
||||
|
||||
LUAU_NOINLINE void checkAliases(const ScopePtr& scope, AstStatBlock* block);
|
||||
|
||||
ControlFlow visitBlockWithoutChildScope(const ScopePtr& scope, AstStatBlock* block);
|
||||
ControlFlow visitBlockWithoutChildScope_DEPRECATED(const ScopePtr& scope, AstStatBlock* block);
|
||||
|
||||
ControlFlow visit(const ScopePtr& scope, AstStat* stat);
|
||||
ControlFlow visit(const ScopePtr& scope, AstStatBlock* block);
|
||||
|
@ -44,21 +44,6 @@ struct LoadDefinitionFileResult
|
||||
|
||||
std::optional<Mode> parseMode(const std::vector<HotComment>& hotcomments);
|
||||
|
||||
std::vector<std::string_view> parsePathExpr(const AstExpr& pathExpr);
|
||||
|
||||
// Exported only for convenient testing.
|
||||
std::optional<ModuleName> pathExprToModuleName(const ModuleName& currentModuleName, const std::vector<std::string_view>& expr);
|
||||
|
||||
/** Try to convert an AST fragment into a ModuleName.
|
||||
* Returns std::nullopt if the expression cannot be resolved. This will most likely happen in cases where
|
||||
* the import path involves some dynamic computation that we cannot see into at typechecking time.
|
||||
*
|
||||
* Unintuitively, weirdly-formulated modules (like game.Parent.Parent.Parent.Foo) will successfully produce a ModuleName
|
||||
* as long as it falls within the permitted syntax. This is ok because we will fail to find the module and produce an
|
||||
* error when we try during typechecking.
|
||||
*/
|
||||
std::optional<ModuleName> pathExprToModuleName(const ModuleName& currentModuleName, const AstExpr& expr);
|
||||
|
||||
struct SourceNode
|
||||
{
|
||||
bool hasDirtySourceModule() const
|
||||
|
@ -20,8 +20,6 @@ struct ModuleResolver
|
||||
virtual ~ModuleResolver() {}
|
||||
|
||||
/** Compute a ModuleName from an AST fragment. This AST fragment is generally the argument to the require() function.
|
||||
*
|
||||
* You probably want to implement this with some variation of pathExprToModuleName.
|
||||
*
|
||||
* @returns The ModuleInfo if the expression is a syntactically legal path.
|
||||
* @returns std::nullopt if we are unable to determine whether or not the expression is a valid path. Type inference will
|
||||
|
@ -9,11 +9,13 @@ namespace Luau
|
||||
{
|
||||
|
||||
struct BuiltinTypes;
|
||||
struct TypeFunctionRuntime;
|
||||
struct UnifierSharedState;
|
||||
struct TypeCheckLimits;
|
||||
|
||||
void checkNonStrict(
|
||||
NotNull<BuiltinTypes> builtinTypes,
|
||||
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
|
||||
NotNull<InternalErrorReporter> ice,
|
||||
NotNull<UnifierSharedState> unifierState,
|
||||
NotNull<const DataFlowGraph> dfg,
|
||||
|
@ -613,20 +613,17 @@ struct TypeFunctionInstanceType
|
||||
std::vector<TypePackId> packArguments;
|
||||
|
||||
std::optional<AstName> userFuncName; // Name of the user-defined type function; only available for UDTFs
|
||||
std::optional<AstExprFunction*> userFuncBody; // Body of the user-defined type function; only available for UDTFs
|
||||
|
||||
TypeFunctionInstanceType(
|
||||
NotNull<const TypeFunction> function,
|
||||
std::vector<TypeId> typeArguments,
|
||||
std::vector<TypePackId> packArguments,
|
||||
std::optional<AstName> userFuncName = std::nullopt,
|
||||
std::optional<AstExprFunction*> userFuncBody = std::nullopt
|
||||
std::optional<AstName> userFuncName = std::nullopt
|
||||
)
|
||||
: function(function)
|
||||
, typeArguments(typeArguments)
|
||||
, packArguments(packArguments)
|
||||
, userFuncName(userFuncName)
|
||||
, userFuncBody(userFuncBody)
|
||||
{
|
||||
}
|
||||
|
||||
@ -1159,6 +1156,10 @@ TypeId freshType(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, S
|
||||
using TypeIdPredicate = std::function<std::optional<TypeId>(TypeId)>;
|
||||
std::vector<TypeId> filterMap(TypeId type, TypeIdPredicate predicate);
|
||||
|
||||
// A tag to mark a type which doesn't derive directly from the root type as overriding the return of `typeof`.
|
||||
// Any classes which derive from this type will have typeof return this type.
|
||||
static constexpr char kTypeofRootTag[] = "typeofRoot";
|
||||
|
||||
void attachTag(TypeId ty, const std::string& tagName);
|
||||
void attachTag(Property& prop, const std::string& tagName);
|
||||
|
||||
|
@ -60,6 +60,7 @@ struct Reasonings
|
||||
|
||||
void check(
|
||||
NotNull<BuiltinTypes> builtinTypes,
|
||||
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
|
||||
NotNull<UnifierSharedState> sharedState,
|
||||
NotNull<TypeCheckLimits> limits,
|
||||
DcrLogger* logger,
|
||||
@ -70,6 +71,7 @@ void check(
|
||||
struct TypeChecker2
|
||||
{
|
||||
NotNull<BuiltinTypes> builtinTypes;
|
||||
NotNull<TypeFunctionRuntime> typeFunctionRuntime;
|
||||
DcrLogger* logger;
|
||||
const NotNull<TypeCheckLimits> limits;
|
||||
const NotNull<InternalErrorReporter> ice;
|
||||
@ -83,12 +85,12 @@ struct TypeChecker2
|
||||
DenseHashSet<TypeId> seenTypeFunctionInstances{nullptr};
|
||||
|
||||
Normalizer normalizer;
|
||||
TypeFunctionRuntime typeFunctionRuntime;
|
||||
Subtyping _subtyping;
|
||||
NotNull<Subtyping> subtyping;
|
||||
|
||||
TypeChecker2(
|
||||
NotNull<BuiltinTypes> builtinTypes,
|
||||
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
|
||||
NotNull<UnifierSharedState> unifierState,
|
||||
NotNull<TypeCheckLimits> limits,
|
||||
DcrLogger* logger,
|
||||
|
@ -12,6 +12,8 @@
|
||||
#include <string>
|
||||
#include <optional>
|
||||
|
||||
struct lua_State;
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
@ -20,11 +22,30 @@ struct TxnLog;
|
||||
struct ConstraintSolver;
|
||||
class Normalizer;
|
||||
|
||||
using StateRef = std::unique_ptr<lua_State, void (*)(lua_State*)>;
|
||||
|
||||
struct TypeFunctionRuntime
|
||||
{
|
||||
TypeFunctionRuntime(NotNull<InternalErrorReporter> ice, NotNull<TypeCheckLimits> limits);
|
||||
~TypeFunctionRuntime();
|
||||
|
||||
// Return value is an error message if registration failed
|
||||
std::optional<std::string> registerFunction(AstStatTypeFunction* function);
|
||||
|
||||
// For user-defined type functions, we store all generated types and packs for the duration of the typecheck
|
||||
TypedAllocator<TypeFunctionType> typeArena;
|
||||
TypedAllocator<TypeFunctionTypePackVar> typePackArena;
|
||||
|
||||
NotNull<InternalErrorReporter> ice;
|
||||
NotNull<TypeCheckLimits> limits;
|
||||
|
||||
StateRef state;
|
||||
|
||||
// Evaluation of type functions should only be performed in the absence of parse errors in the source module
|
||||
bool allowEvaluation = true;
|
||||
|
||||
private:
|
||||
void prepareState();
|
||||
};
|
||||
|
||||
struct TypeFunctionContext
|
||||
@ -43,7 +64,6 @@ struct TypeFunctionContext
|
||||
const Constraint* constraint;
|
||||
|
||||
std::optional<AstName> userFuncName; // Name of the user-defined type function; only available for UDTFs
|
||||
std::optional<AstExprFunction*> userFuncBody; // Body of the user-defined type function; only available for UDTFs
|
||||
|
||||
TypeFunctionContext(NotNull<ConstraintSolver> cs, NotNull<Scope> scope, NotNull<const Constraint> constraint);
|
||||
|
||||
|
@ -51,6 +51,8 @@ struct Index
|
||||
/// Represents fields of a type or pack that contain a type.
|
||||
enum class TypeField
|
||||
{
|
||||
/// The table of a metatable type.
|
||||
Table,
|
||||
/// The metatable of a type. This could be a metatable type, a primitive
|
||||
/// type, a class type, or perhaps even a string singleton type.
|
||||
Metatable,
|
||||
|
@ -13,6 +13,8 @@
|
||||
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauDocumentationAtPosition, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
@ -509,6 +511,38 @@ static std::optional<DocumentationSymbol> checkOverloadedDocumentationSymbol(
|
||||
return documentationSymbol;
|
||||
}
|
||||
|
||||
static std::optional<DocumentationSymbol> getMetatableDocumentation(
|
||||
const Module& module,
|
||||
AstExpr* parentExpr,
|
||||
const TableType* mtable,
|
||||
const AstName& index
|
||||
)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauDocumentationAtPosition);
|
||||
auto indexIt = mtable->props.find("__index");
|
||||
if (indexIt == mtable->props.end())
|
||||
return std::nullopt;
|
||||
|
||||
TypeId followed = follow(indexIt->second.type());
|
||||
const TableType* ttv = get<TableType>(followed);
|
||||
if (!ttv)
|
||||
return std::nullopt;
|
||||
|
||||
auto propIt = ttv->props.find(index.value);
|
||||
if (propIt == ttv->props.end())
|
||||
return std::nullopt;
|
||||
|
||||
if (FFlag::LuauSolverV2)
|
||||
{
|
||||
if (auto ty = propIt->second.readTy)
|
||||
return checkOverloadedDocumentationSymbol(module, *ty, parentExpr, propIt->second.documentationSymbol);
|
||||
}
|
||||
else
|
||||
return checkOverloadedDocumentationSymbol(module, propIt->second.type(), parentExpr, propIt->second.documentationSymbol);
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<DocumentationSymbol> getDocumentationSymbolAtPosition(const SourceModule& source, const Module& module, Position position)
|
||||
{
|
||||
std::vector<AstNode*> ancestry = findAstAncestryOfPosition(source, position);
|
||||
@ -541,15 +575,50 @@ std::optional<DocumentationSymbol> getDocumentationSymbolAtPosition(const Source
|
||||
}
|
||||
else if (const ClassType* ctv = get<ClassType>(parentTy))
|
||||
{
|
||||
if (auto propIt = ctv->props.find(indexName->index.value); propIt != ctv->props.end())
|
||||
if (FFlag::LuauDocumentationAtPosition)
|
||||
{
|
||||
if (FFlag::LuauSolverV2)
|
||||
while (ctv)
|
||||
{
|
||||
if (auto ty = propIt->second.readTy)
|
||||
return checkOverloadedDocumentationSymbol(module, *ty, parentExpr, propIt->second.documentationSymbol);
|
||||
if (auto propIt = ctv->props.find(indexName->index.value); propIt != ctv->props.end())
|
||||
{
|
||||
if (FFlag::LuauSolverV2)
|
||||
{
|
||||
if (auto ty = propIt->second.readTy)
|
||||
return checkOverloadedDocumentationSymbol(module, *ty, parentExpr, propIt->second.documentationSymbol);
|
||||
}
|
||||
else
|
||||
return checkOverloadedDocumentationSymbol(
|
||||
module, propIt->second.type(), parentExpr, propIt->second.documentationSymbol
|
||||
);
|
||||
}
|
||||
ctv = ctv->parent ? Luau::get<Luau::ClassType>(*ctv->parent) : nullptr;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (auto propIt = ctv->props.find(indexName->index.value); propIt != ctv->props.end())
|
||||
{
|
||||
if (FFlag::LuauSolverV2)
|
||||
{
|
||||
if (auto ty = propIt->second.readTy)
|
||||
return checkOverloadedDocumentationSymbol(module, *ty, parentExpr, propIt->second.documentationSymbol);
|
||||
}
|
||||
else
|
||||
return checkOverloadedDocumentationSymbol(
|
||||
module, propIt->second.type(), parentExpr, propIt->second.documentationSymbol
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (FFlag::LuauDocumentationAtPosition)
|
||||
{
|
||||
if (const PrimitiveType* ptv = get<PrimitiveType>(parentTy); ptv && ptv->metatable)
|
||||
{
|
||||
if (auto mtable = get<TableType>(*ptv->metatable))
|
||||
{
|
||||
if (std::optional<std::string> docSymbol = getMetatableDocumentation(module, parentExpr, mtable, indexName->index))
|
||||
return docSymbol;
|
||||
}
|
||||
else
|
||||
return checkOverloadedDocumentationSymbol(module, propIt->second.type(), parentExpr, propIt->second.documentationSymbol);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -149,7 +149,10 @@ static bool checkTypeMatch(TypeId subTy, TypeId superTy, NotNull<Scope> scope, T
|
||||
|
||||
if (FFlag::LuauSolverV2)
|
||||
{
|
||||
TypeFunctionRuntime typeFunctionRuntime; // TODO: maybe subtyping checks should not invoke user-defined type function runtime
|
||||
TypeCheckLimits limits;
|
||||
TypeFunctionRuntime typeFunctionRuntime{
|
||||
NotNull{&iceReporter}, NotNull{&limits}
|
||||
}; // TODO: maybe subtyping checks should not invoke user-defined type function runtime
|
||||
|
||||
if (FFlag::LuauAutocompleteNewSolverLimit)
|
||||
{
|
||||
|
@ -29,6 +29,7 @@
|
||||
LUAU_FASTINT(LuauCheckRecursionLimit);
|
||||
LUAU_FASTFLAG(DebugLuauLogSolverToJson);
|
||||
LUAU_FASTFLAG(DebugLuauMagicTypes);
|
||||
LUAU_DYNAMIC_FASTINT(LuauTypeSolverRelease);
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -191,6 +192,7 @@ bool hasFreeType(TypeId ty)
|
||||
ConstraintGenerator::ConstraintGenerator(
|
||||
ModulePtr module,
|
||||
NotNull<Normalizer> normalizer,
|
||||
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
|
||||
NotNull<ModuleResolver> moduleResolver,
|
||||
NotNull<BuiltinTypes> builtinTypes,
|
||||
NotNull<InternalErrorReporter> ice,
|
||||
@ -206,6 +208,7 @@ ConstraintGenerator::ConstraintGenerator(
|
||||
, rootScope(nullptr)
|
||||
, dfg(dfg)
|
||||
, normalizer(normalizer)
|
||||
, typeFunctionRuntime(typeFunctionRuntime)
|
||||
, moduleResolver(moduleResolver)
|
||||
, ice(ice)
|
||||
, globalScope(globalScope)
|
||||
@ -237,7 +240,8 @@ void ConstraintGenerator::visitModuleRoot(AstStatBlock* block)
|
||||
|
||||
Checkpoint start = checkpoint(this);
|
||||
|
||||
ControlFlow cf = visitBlockWithoutChildScope(scope, block);
|
||||
ControlFlow cf =
|
||||
DFInt::LuauTypeSolverRelease >= 646 ? visitBlockWithoutChildScope(scope, block) : visitBlockWithoutChildScope_DEPRECATED(scope, block);
|
||||
if (cf == ControlFlow::None)
|
||||
addConstraint(scope, block->location, PackSubtypeConstraint{builtinTypes->emptyTypePack, rootScope->returnType});
|
||||
|
||||
@ -643,6 +647,109 @@ void ConstraintGenerator::applyRefinements(const ScopePtr& scope, Location locat
|
||||
addConstraint(scope, location, c);
|
||||
}
|
||||
|
||||
void ConstraintGenerator::checkAliases(const ScopePtr& scope, AstStatBlock* block)
|
||||
{
|
||||
std::unordered_map<Name, Location> aliasDefinitionLocations;
|
||||
|
||||
// In order to enable mutually-recursive type aliases, we need to
|
||||
// populate the type bindings before we actually check any of the
|
||||
// alias statements.
|
||||
for (AstStat* stat : block->body)
|
||||
{
|
||||
if (auto alias = stat->as<AstStatTypeAlias>())
|
||||
{
|
||||
if (scope->exportedTypeBindings.count(alias->name.value) || scope->privateTypeBindings.count(alias->name.value))
|
||||
{
|
||||
auto it = aliasDefinitionLocations.find(alias->name.value);
|
||||
LUAU_ASSERT(it != aliasDefinitionLocations.end());
|
||||
reportError(alias->location, DuplicateTypeDefinition{alias->name.value, it->second});
|
||||
continue;
|
||||
}
|
||||
|
||||
// A type alias might have no name if the code is syntactically
|
||||
// illegal. We mustn't prepopulate anything in this case.
|
||||
if (alias->name == kParseNameError || alias->name == "typeof")
|
||||
continue;
|
||||
|
||||
ScopePtr defnScope = childScope(alias, scope);
|
||||
|
||||
TypeId initialType = arena->addType(BlockedType{});
|
||||
TypeFun initialFun{initialType};
|
||||
|
||||
for (const auto& [name, gen] : createGenerics(defnScope, alias->generics, /* useCache */ true))
|
||||
{
|
||||
initialFun.typeParams.push_back(gen);
|
||||
}
|
||||
|
||||
for (const auto& [name, genPack] : createGenericPacks(defnScope, alias->genericPacks, /* useCache */ true))
|
||||
{
|
||||
initialFun.typePackParams.push_back(genPack);
|
||||
}
|
||||
|
||||
if (alias->exported)
|
||||
scope->exportedTypeBindings[alias->name.value] = std::move(initialFun);
|
||||
else
|
||||
scope->privateTypeBindings[alias->name.value] = std::move(initialFun);
|
||||
|
||||
astTypeAliasDefiningScopes[alias] = defnScope;
|
||||
aliasDefinitionLocations[alias->name.value] = alias->location;
|
||||
}
|
||||
else if (auto function = stat->as<AstStatTypeFunction>())
|
||||
{
|
||||
// If a type function w/ same name has already been defined, error for having duplicates
|
||||
if (scope->exportedTypeBindings.count(function->name.value) || scope->privateTypeBindings.count(function->name.value))
|
||||
{
|
||||
auto it = aliasDefinitionLocations.find(function->name.value);
|
||||
LUAU_ASSERT(it != aliasDefinitionLocations.end());
|
||||
reportError(function->location, DuplicateTypeDefinition{function->name.value, it->second});
|
||||
continue;
|
||||
}
|
||||
|
||||
if (scope->parent != globalScope)
|
||||
{
|
||||
reportError(function->location, GenericError{"Local user-defined functions are not supported yet"});
|
||||
continue;
|
||||
}
|
||||
|
||||
ScopePtr defnScope = childScope(function, scope);
|
||||
|
||||
// Create TypeFunctionInstanceType
|
||||
|
||||
std::vector<TypeId> typeParams;
|
||||
typeParams.reserve(function->body->args.size);
|
||||
|
||||
std::vector<GenericTypeDefinition> quantifiedTypeParams;
|
||||
quantifiedTypeParams.reserve(function->body->args.size);
|
||||
|
||||
for (size_t i = 0; i < function->body->args.size; i++)
|
||||
{
|
||||
std::string name = format("T%zu", i);
|
||||
TypeId ty = arena->addType(GenericType{name});
|
||||
typeParams.push_back(ty);
|
||||
|
||||
GenericTypeDefinition genericTy{ty};
|
||||
quantifiedTypeParams.push_back(genericTy);
|
||||
}
|
||||
|
||||
if (std::optional<std::string> error = typeFunctionRuntime->registerFunction(function))
|
||||
reportError(function->location, GenericError{*error});
|
||||
|
||||
TypeId typeFunctionTy = arena->addType(TypeFunctionInstanceType{
|
||||
NotNull{&builtinTypeFunctions().userFunc},
|
||||
std::move(typeParams),
|
||||
{},
|
||||
function->name,
|
||||
});
|
||||
|
||||
TypeFun typeFunction{std::move(quantifiedTypeParams), typeFunctionTy};
|
||||
|
||||
// Set type bindings and definition locations for this user-defined type function
|
||||
scope->privateTypeBindings[function->name.value] = std::move(typeFunction);
|
||||
aliasDefinitionLocations[function->name.value] = function->location;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ControlFlow ConstraintGenerator::visitBlockWithoutChildScope(const ScopePtr& scope, AstStatBlock* block)
|
||||
{
|
||||
RecursionCounter counter{&recursionCount};
|
||||
@ -653,6 +760,29 @@ ControlFlow ConstraintGenerator::visitBlockWithoutChildScope(const ScopePtr& sco
|
||||
return ControlFlow::None;
|
||||
}
|
||||
|
||||
checkAliases(scope, block);
|
||||
|
||||
std::optional<ControlFlow> firstControlFlow;
|
||||
for (AstStat* stat : block->body)
|
||||
{
|
||||
ControlFlow cf = visit(scope, stat);
|
||||
if (cf != ControlFlow::None && !firstControlFlow)
|
||||
firstControlFlow = cf;
|
||||
}
|
||||
|
||||
return firstControlFlow.value_or(ControlFlow::None);
|
||||
}
|
||||
|
||||
ControlFlow ConstraintGenerator::visitBlockWithoutChildScope_DEPRECATED(const ScopePtr& scope, AstStatBlock* block)
|
||||
{
|
||||
RecursionCounter counter{&recursionCount};
|
||||
|
||||
if (recursionCount >= FInt::LuauCheckRecursionLimit)
|
||||
{
|
||||
reportCodeTooComplex(block->location);
|
||||
return ControlFlow::None;
|
||||
}
|
||||
|
||||
std::unordered_map<Name, Location> aliasDefinitionLocations;
|
||||
|
||||
// In order to enable mutually-recursive type aliases, we need to
|
||||
@ -709,6 +839,12 @@ ControlFlow ConstraintGenerator::visitBlockWithoutChildScope(const ScopePtr& sco
|
||||
continue;
|
||||
}
|
||||
|
||||
if (scope->parent != globalScope)
|
||||
{
|
||||
reportError(function->location, GenericError{"Local user-defined functions are not supported yet"});
|
||||
continue;
|
||||
}
|
||||
|
||||
ScopePtr defnScope = childScope(function, scope);
|
||||
|
||||
// Create TypeFunctionInstanceType
|
||||
@ -729,12 +865,14 @@ ControlFlow ConstraintGenerator::visitBlockWithoutChildScope(const ScopePtr& sco
|
||||
quantifiedTypeParams.push_back(genericTy);
|
||||
}
|
||||
|
||||
if (std::optional<std::string> error = typeFunctionRuntime->registerFunction(function))
|
||||
reportError(function->location, GenericError{*error});
|
||||
|
||||
TypeId typeFunctionTy = arena->addType(TypeFunctionInstanceType{
|
||||
NotNull{&builtinTypeFunctions().userFunc},
|
||||
std::move(typeParams),
|
||||
{},
|
||||
function->name,
|
||||
function->body,
|
||||
});
|
||||
|
||||
TypeFun typeFunction{std::move(quantifiedTypeParams), typeFunctionTy};
|
||||
@ -1091,7 +1229,10 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatRepeat* rep
|
||||
{
|
||||
ScopePtr repeatScope = childScope(repeat, scope);
|
||||
|
||||
visitBlockWithoutChildScope(repeatScope, repeat->body);
|
||||
if (DFInt::LuauTypeSolverRelease >= 646)
|
||||
visitBlockWithoutChildScope(repeatScope, repeat->body);
|
||||
else
|
||||
visitBlockWithoutChildScope_DEPRECATED(repeatScope, repeat->body);
|
||||
|
||||
check(repeatScope, repeat->condition);
|
||||
|
||||
@ -1265,7 +1406,8 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatBlock* bloc
|
||||
{
|
||||
ScopePtr innerScope = childScope(block, scope);
|
||||
|
||||
ControlFlow flow = visitBlockWithoutChildScope(innerScope, block);
|
||||
ControlFlow flow = DFInt::LuauTypeSolverRelease >= 646 ? visitBlockWithoutChildScope(innerScope, block)
|
||||
: visitBlockWithoutChildScope_DEPRECATED(innerScope, block);
|
||||
|
||||
// An AstStatBlock has linear control flow, i.e. one entry and one exit, so we can inherit
|
||||
// all the changes to the environment occurred by the statements in that block.
|
||||
@ -1456,7 +1598,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeFunctio
|
||||
TypeFun typeFunction = bindingIt->second;
|
||||
|
||||
// Adding typeAliasExpansionConstraint on user-defined type function for the constraint solver
|
||||
if (auto typeFunctionTy = get<TypeFunctionInstanceType>(typeFunction.type))
|
||||
if (auto typeFunctionTy = get<TypeFunctionInstanceType>(DFInt::LuauTypeSolverRelease >= 646 ? follow(typeFunction.type) : typeFunction.type))
|
||||
{
|
||||
TypeId expansionTy = arena->addType(PendingExpansionType{{}, function->name, typeFunctionTy->typeArguments, typeFunctionTy->packArguments});
|
||||
addConstraint(scope, function->location, TypeAliasExpansionConstraint{/* target */ expansionTy});
|
||||
@ -2511,7 +2653,7 @@ std::tuple<TypeId, TypeId, RefinementId> ConstraintGenerator::checkBinary(
|
||||
TypeId ty = follow(typeFun->type);
|
||||
|
||||
// We're only interested in the root class of any classes.
|
||||
if (auto ctv = get<ClassType>(ty); ctv && ctv->parent == builtinTypes->classType)
|
||||
if (auto ctv = get<ClassType>(ty); ctv && (ctv->parent == builtinTypes->classType || hasTag(ty, kTypeofRootTag)))
|
||||
discriminantTy = ty;
|
||||
}
|
||||
|
||||
@ -2944,7 +3086,8 @@ ConstraintGenerator::FunctionSignature ConstraintGenerator::checkFunctionSignatu
|
||||
void ConstraintGenerator::checkFunctionBody(const ScopePtr& scope, AstExprFunction* fn)
|
||||
{
|
||||
// If it is possible for execution to reach the end of the function, the return type must be compatible with ()
|
||||
ControlFlow cf = visitBlockWithoutChildScope(scope, fn->body);
|
||||
ControlFlow cf =
|
||||
DFInt::LuauTypeSolverRelease >= 646 ? visitBlockWithoutChildScope(scope, fn->body) : visitBlockWithoutChildScope_DEPRECATED(scope, fn->body);
|
||||
if (cf == ControlFlow::None)
|
||||
addConstraint(scope, fn->location, PackSubtypeConstraint{builtinTypes->emptyTypePack, scope->returnType});
|
||||
}
|
||||
|
@ -915,9 +915,19 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul
|
||||
|
||||
auto bindResult = [this, &c, constraint](TypeId result)
|
||||
{
|
||||
LUAU_ASSERT(get<PendingExpansionType>(c.target));
|
||||
shiftReferences(c.target, result);
|
||||
bind(constraint, c.target, result);
|
||||
if (DFInt::LuauTypeSolverRelease >= 646)
|
||||
{
|
||||
auto cTarget = follow(c.target);
|
||||
LUAU_ASSERT(get<PendingExpansionType>(cTarget));
|
||||
shiftReferences(cTarget, result);
|
||||
bind(constraint, cTarget, result);
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_ASSERT(get<PendingExpansionType>(c.target));
|
||||
shiftReferences(c.target, result);
|
||||
bind(constraint, c.target, result);
|
||||
}
|
||||
};
|
||||
|
||||
std::optional<TypeFun> tf = (petv->prefix) ? constraint->scope->lookupImportedType(petv->prefix->value, petv->name.value)
|
||||
@ -945,7 +955,7 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul
|
||||
// Due to how pending expansion types and TypeFun's are created
|
||||
// If this check passes, we have created a cyclic / corecursive type alias
|
||||
// of size 0
|
||||
TypeId lhs = c.target;
|
||||
TypeId lhs = DFInt::LuauTypeSolverRelease >= 646 ? follow(c.target) : c.target;
|
||||
TypeId rhs = tf->type;
|
||||
if (occursCheck(lhs, rhs))
|
||||
{
|
||||
|
@ -18,8 +18,6 @@
|
||||
|
||||
LUAU_FASTINTVARIABLE(LuauIndentTypeMismatchMaxTypeLength, 10)
|
||||
|
||||
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauImproveNonFunctionCallError, false)
|
||||
|
||||
static std::string wrongNumberOfArgsString(
|
||||
size_t expectedCount,
|
||||
std::optional<size_t> maximumCount,
|
||||
@ -408,35 +406,30 @@ struct ErrorConverter
|
||||
|
||||
std::string operator()(const Luau::CannotCallNonFunction& e) const
|
||||
{
|
||||
if (DFFlag::LuauImproveNonFunctionCallError)
|
||||
if (auto unionTy = get<UnionType>(follow(e.ty)))
|
||||
{
|
||||
if (auto unionTy = get<UnionType>(follow(e.ty)))
|
||||
std::string err = "Cannot call a value of the union type:";
|
||||
|
||||
for (auto option : unionTy)
|
||||
{
|
||||
std::string err = "Cannot call a value of the union type:";
|
||||
option = follow(option);
|
||||
|
||||
for (auto option : unionTy)
|
||||
if (get<FunctionType>(option) || findCallMetamethod(option))
|
||||
{
|
||||
option = follow(option);
|
||||
|
||||
if (get<FunctionType>(option) || findCallMetamethod(option))
|
||||
{
|
||||
err += "\n | " + toString(option);
|
||||
continue;
|
||||
}
|
||||
|
||||
// early-exit if we find something that isn't callable in the union.
|
||||
return "Cannot call a value of type " + toString(option) + " in union:\n " + toString(e.ty);
|
||||
err += "\n | " + toString(option);
|
||||
continue;
|
||||
}
|
||||
|
||||
err += "\nWe are unable to determine the appropriate result type for such a call.";
|
||||
|
||||
return err;
|
||||
// early-exit if we find something that isn't callable in the union.
|
||||
return "Cannot call a value of type " + toString(option) + " in union:\n " + toString(e.ty);
|
||||
}
|
||||
|
||||
return "Cannot call a value of type " + toString(e.ty);
|
||||
err += "\nWe are unable to determine the appropriate result type for such a call.";
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
return "Cannot call non-function " + toString(e.ty);
|
||||
return "Cannot call a value of type " + toString(e.ty);
|
||||
}
|
||||
std::string operator()(const Luau::ExtraInformation& e) const
|
||||
{
|
||||
|
@ -45,6 +45,7 @@ LUAU_FASTFLAGVARIABLE(DebugLuauForbidInternalTypes, false)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauForceStrictMode, false)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauForceNonStrictMode, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauSourceModuleUpdatedWithSelectedMode, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauUserDefinedTypeFunctionNoEvaluation, false)
|
||||
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauRunCustomModuleChecks, false)
|
||||
|
||||
LUAU_FASTFLAG(StudioReportLuauAny2)
|
||||
@ -205,72 +206,6 @@ LoadDefinitionFileResult Frontend::loadDefinitionFile(
|
||||
return LoadDefinitionFileResult{true, parseResult, sourceModule, checkedModule};
|
||||
}
|
||||
|
||||
std::vector<std::string_view> parsePathExpr(const AstExpr& pathExpr)
|
||||
{
|
||||
const AstExprIndexName* indexName = pathExpr.as<AstExprIndexName>();
|
||||
if (!indexName)
|
||||
return {};
|
||||
|
||||
std::vector<std::string_view> segments{indexName->index.value};
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (AstExprIndexName* in = indexName->expr->as<AstExprIndexName>())
|
||||
{
|
||||
segments.push_back(in->index.value);
|
||||
indexName = in;
|
||||
continue;
|
||||
}
|
||||
else if (AstExprGlobal* indexNameAsGlobal = indexName->expr->as<AstExprGlobal>())
|
||||
{
|
||||
segments.push_back(indexNameAsGlobal->name.value);
|
||||
break;
|
||||
}
|
||||
else if (AstExprLocal* indexNameAsLocal = indexName->expr->as<AstExprLocal>())
|
||||
{
|
||||
segments.push_back(indexNameAsLocal->local->name.value);
|
||||
break;
|
||||
}
|
||||
else
|
||||
return {};
|
||||
}
|
||||
|
||||
std::reverse(segments.begin(), segments.end());
|
||||
return segments;
|
||||
}
|
||||
|
||||
std::optional<std::string> pathExprToModuleName(const ModuleName& currentModuleName, const std::vector<std::string_view>& segments)
|
||||
{
|
||||
if (segments.empty())
|
||||
return std::nullopt;
|
||||
|
||||
std::vector<std::string_view> result;
|
||||
|
||||
auto it = segments.begin();
|
||||
|
||||
if (*it == "script" && !currentModuleName.empty())
|
||||
{
|
||||
result = split(currentModuleName, '/');
|
||||
++it;
|
||||
}
|
||||
|
||||
for (; it != segments.end(); ++it)
|
||||
{
|
||||
if (result.size() > 1 && *it == "Parent")
|
||||
result.pop_back();
|
||||
else
|
||||
result.push_back(*it);
|
||||
}
|
||||
|
||||
return join(result, "/");
|
||||
}
|
||||
|
||||
std::optional<std::string> pathExprToModuleName(const ModuleName& currentModuleName, const AstExpr& pathExpr)
|
||||
{
|
||||
std::vector<std::string_view> segments = parsePathExpr(pathExpr);
|
||||
return pathExprToModuleName(currentModuleName, segments);
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
@ -1383,11 +1318,15 @@ ModulePtr check(
|
||||
unifierState.counters.iterationLimit = limits.unifierIterationLimit.value_or(FInt::LuauTypeInferIterationLimit);
|
||||
|
||||
Normalizer normalizer{&result->internalTypes, builtinTypes, NotNull{&unifierState}};
|
||||
TypeFunctionRuntime typeFunctionRuntime;
|
||||
TypeFunctionRuntime typeFunctionRuntime{iceHandler, NotNull{&limits}};
|
||||
|
||||
if (FFlag::LuauUserDefinedTypeFunctionNoEvaluation)
|
||||
typeFunctionRuntime.allowEvaluation = sourceModule.parseErrors.empty();
|
||||
|
||||
ConstraintGenerator cg{
|
||||
result,
|
||||
NotNull{&normalizer},
|
||||
NotNull{&typeFunctionRuntime},
|
||||
moduleResolver,
|
||||
builtinTypes,
|
||||
iceHandler,
|
||||
@ -1463,12 +1402,23 @@ ModulePtr check(
|
||||
switch (mode)
|
||||
{
|
||||
case Mode::Nonstrict:
|
||||
Luau::checkNonStrict(builtinTypes, iceHandler, NotNull{&unifierState}, NotNull{&dfg}, NotNull{&limits}, sourceModule, result.get());
|
||||
Luau::checkNonStrict(
|
||||
builtinTypes,
|
||||
NotNull{&typeFunctionRuntime},
|
||||
iceHandler,
|
||||
NotNull{&unifierState},
|
||||
NotNull{&dfg},
|
||||
NotNull{&limits},
|
||||
sourceModule,
|
||||
result.get()
|
||||
);
|
||||
break;
|
||||
case Mode::Definition:
|
||||
// fallthrough intentional
|
||||
case Mode::Strict:
|
||||
Luau::check(builtinTypes, NotNull{&unifierState}, NotNull{&limits}, logger.get(), sourceModule, result.get());
|
||||
Luau::check(
|
||||
builtinTypes, NotNull{&typeFunctionRuntime}, NotNull{&unifierState}, NotNull{&limits}, logger.get(), sourceModule, result.get()
|
||||
);
|
||||
break;
|
||||
case Mode::NoCheck:
|
||||
break;
|
||||
|
@ -528,7 +528,12 @@ struct TypeCacher : TypeOnceVisitor
|
||||
DenseHashSet<TypePackId> uncacheablePacks{nullptr};
|
||||
|
||||
explicit TypeCacher(NotNull<DenseHashSet<TypeId>> cachedTypes)
|
||||
: TypeOnceVisitor(/* skipBoundTypes */ true)
|
||||
// CLI-120975: once we roll out release 646, we _want_ to visit bound
|
||||
// types to ensure they're marked as uncacheable if the types they are
|
||||
// bound to are also uncacheable. Hence: if LuauTypeSolverRelease is
|
||||
// less than 646, skip bound types (the prior behavior). Otherwise,
|
||||
// do not skip bound types.
|
||||
: TypeOnceVisitor(/* skipBoundTypes */ DFInt::LuauTypeSolverRelease < 646)
|
||||
, cachedTypes(cachedTypes)
|
||||
{
|
||||
}
|
||||
@ -565,9 +570,33 @@ struct TypeCacher : TypeOnceVisitor
|
||||
|
||||
bool visit(TypeId ty) override
|
||||
{
|
||||
if (isUncacheable(ty) || isCached(ty))
|
||||
if (DFInt::LuauTypeSolverRelease >= 646)
|
||||
{
|
||||
// NOTE: `TypeCacher` should explicitly visit _all_ types and type packs,
|
||||
// otherwise it's prone to marking types that cannot be cached as
|
||||
// cacheable.
|
||||
LUAU_ASSERT(false);
|
||||
LUAU_UNREACHABLE();
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool visit(TypeId ty, const BoundType& btv) override
|
||||
{
|
||||
if (DFInt::LuauTypeSolverRelease >= 646)
|
||||
{
|
||||
traverse(btv.boundTo);
|
||||
if (isUncacheable(btv.boundTo))
|
||||
markUncacheable(ty);
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool visit(TypeId ty, const FreeType& ft) override
|
||||
@ -592,6 +621,19 @@ struct TypeCacher : TypeOnceVisitor
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(TypeId ty, const ErrorType&) override
|
||||
{
|
||||
if (DFInt::LuauTypeSolverRelease >= 646)
|
||||
{
|
||||
cache(ty);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool visit(TypeId ty, const PrimitiveType&) override
|
||||
{
|
||||
cache(ty);
|
||||
@ -729,6 +771,24 @@ struct TypeCacher : TypeOnceVisitor
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(TypeId ty, const MetatableType& mtv) override
|
||||
{
|
||||
if (DFInt::LuauTypeSolverRelease >= 646)
|
||||
{
|
||||
traverse(mtv.table);
|
||||
traverse(mtv.metatable);
|
||||
if (isUncacheable(mtv.table) || isUncacheable(mtv.metatable))
|
||||
markUncacheable(ty);
|
||||
else
|
||||
cache(ty);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool visit(TypeId ty, const ClassType&) override
|
||||
{
|
||||
cache(ty);
|
||||
@ -843,12 +903,38 @@ struct TypeCacher : TypeOnceVisitor
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(TypePackId tp) override
|
||||
{
|
||||
if (DFInt::LuauTypeSolverRelease >= 646)
|
||||
{
|
||||
// NOTE: `TypeCacher` should explicitly visit _all_ types and type packs,
|
||||
// otherwise it's prone to marking types that cannot be cached as
|
||||
// cacheable, which will segfault down the line.
|
||||
LUAU_ASSERT(false);
|
||||
LUAU_UNREACHABLE();
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool visit(TypePackId tp, const FreeTypePack&) override
|
||||
{
|
||||
markUncacheable(tp);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(TypePackId tp, const GenericTypePack& gtp) override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool visit(TypePackId tp, const Unifiable::Error& etp) override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool visit(TypePackId tp, const VariadicTypePack& vtp) override
|
||||
{
|
||||
if (isUncacheable(tp))
|
||||
@ -884,6 +970,27 @@ struct TypeCacher : TypeOnceVisitor
|
||||
return true;
|
||||
}
|
||||
|
||||
bool visit(TypePackId tp, const TypePack& typ) override
|
||||
{
|
||||
if (DFInt::LuauTypeSolverRelease >= 646)
|
||||
{
|
||||
bool uncacheable = false;
|
||||
for (TypeId ty : typ.head)
|
||||
{
|
||||
traverse(ty);
|
||||
uncacheable |= isUncacheable(ty);
|
||||
}
|
||||
if (typ.tail)
|
||||
{
|
||||
traverse(*typ.tail);
|
||||
uncacheable |= isUncacheable(*typ.tail);
|
||||
}
|
||||
if (uncacheable)
|
||||
markUncacheable(tp);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
std::optional<TypeId> generalize(
|
||||
|
@ -146,7 +146,20 @@ struct ClonePublicInterface : Substitution
|
||||
{
|
||||
if (auto freety = getMutable<FreeType>(result))
|
||||
{
|
||||
freety->scope = nullptr;
|
||||
if (DFInt::LuauTypeSolverRelease >= 646)
|
||||
{
|
||||
module->errors.emplace_back(
|
||||
freety->scope->location,
|
||||
module->name,
|
||||
InternalError{"Free type is escaping its module; please report this bug at "
|
||||
"https://github.com/luau-lang/luau/issues"}
|
||||
);
|
||||
result = builtinTypes->errorRecoveryType();
|
||||
}
|
||||
else
|
||||
{
|
||||
freety->scope = nullptr;
|
||||
}
|
||||
}
|
||||
else if (auto genericty = getMutable<GenericType>(result))
|
||||
{
|
||||
@ -159,7 +172,35 @@ struct ClonePublicInterface : Substitution
|
||||
|
||||
TypePackId clean(TypePackId tp) override
|
||||
{
|
||||
return clone(tp);
|
||||
if (FFlag::LuauSolverV2 && DFInt::LuauTypeSolverRelease >= 645)
|
||||
{
|
||||
auto clonedTp = clone(tp);
|
||||
if (auto ftp = getMutable<FreeTypePack>(clonedTp))
|
||||
{
|
||||
|
||||
if (DFInt::LuauTypeSolverRelease >= 646)
|
||||
{
|
||||
module->errors.emplace_back(
|
||||
ftp->scope->location,
|
||||
module->name,
|
||||
InternalError{"Free type pack is escaping its module; please report this bug at "
|
||||
"https://github.com/luau-lang/luau/issues"}
|
||||
);
|
||||
clonedTp = builtinTypes->errorRecoveryTypePack();
|
||||
}
|
||||
else
|
||||
{
|
||||
ftp->scope = nullptr;
|
||||
}
|
||||
}
|
||||
else if (auto gtp = getMutable<GenericTypePack>(clonedTp))
|
||||
gtp->scope = nullptr;
|
||||
return clonedTp;
|
||||
}
|
||||
else
|
||||
{
|
||||
return clone(tp);
|
||||
}
|
||||
}
|
||||
|
||||
TypeId cloneType(TypeId ty)
|
||||
|
@ -154,13 +154,12 @@ private:
|
||||
|
||||
struct NonStrictTypeChecker
|
||||
{
|
||||
|
||||
NotNull<BuiltinTypes> builtinTypes;
|
||||
NotNull<TypeFunctionRuntime> typeFunctionRuntime;
|
||||
const NotNull<InternalErrorReporter> ice;
|
||||
NotNull<TypeArena> arena;
|
||||
Module* module;
|
||||
Normalizer normalizer;
|
||||
TypeFunctionRuntime typeFunctionRuntime;
|
||||
Subtyping subtyping;
|
||||
NotNull<const DataFlowGraph> dfg;
|
||||
DenseHashSet<TypeId> noTypeFunctionErrors{nullptr};
|
||||
@ -172,6 +171,7 @@ struct NonStrictTypeChecker
|
||||
NonStrictTypeChecker(
|
||||
NotNull<TypeArena> arena,
|
||||
NotNull<BuiltinTypes> builtinTypes,
|
||||
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
|
||||
const NotNull<InternalErrorReporter> ice,
|
||||
NotNull<UnifierSharedState> unifierState,
|
||||
NotNull<const DataFlowGraph> dfg,
|
||||
@ -179,11 +179,12 @@ struct NonStrictTypeChecker
|
||||
Module* module
|
||||
)
|
||||
: builtinTypes(builtinTypes)
|
||||
, typeFunctionRuntime(typeFunctionRuntime)
|
||||
, ice(ice)
|
||||
, arena(arena)
|
||||
, module(module)
|
||||
, normalizer{arena, builtinTypes, unifierState, /* cache inhabitance */ true}
|
||||
, subtyping{builtinTypes, arena, NotNull(&normalizer), NotNull(&typeFunctionRuntime), ice}
|
||||
, subtyping{builtinTypes, arena, NotNull(&normalizer), typeFunctionRuntime, ice}
|
||||
, dfg(dfg)
|
||||
, limits(limits)
|
||||
{
|
||||
@ -228,14 +229,13 @@ struct NonStrictTypeChecker
|
||||
if (noTypeFunctionErrors.find(instance))
|
||||
return instance;
|
||||
|
||||
ErrorVec errors =
|
||||
reduceTypeFunctions(
|
||||
instance,
|
||||
location,
|
||||
TypeFunctionContext{arena, builtinTypes, stack.back(), NotNull{&normalizer}, NotNull{&typeFunctionRuntime}, ice, limits},
|
||||
true
|
||||
)
|
||||
.errors;
|
||||
ErrorVec errors = reduceTypeFunctions(
|
||||
instance,
|
||||
location,
|
||||
TypeFunctionContext{arena, builtinTypes, stack.back(), NotNull{&normalizer}, typeFunctionRuntime, ice, limits},
|
||||
true
|
||||
)
|
||||
.errors;
|
||||
|
||||
if (errors.empty())
|
||||
noTypeFunctionErrors.insert(instance);
|
||||
@ -760,6 +760,7 @@ private:
|
||||
|
||||
void checkNonStrict(
|
||||
NotNull<BuiltinTypes> builtinTypes,
|
||||
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
|
||||
NotNull<InternalErrorReporter> ice,
|
||||
NotNull<UnifierSharedState> unifierState,
|
||||
NotNull<const DataFlowGraph> dfg,
|
||||
@ -770,7 +771,7 @@ void checkNonStrict(
|
||||
{
|
||||
LUAU_TIMETRACE_SCOPE("checkNonStrict", "Typechecking");
|
||||
|
||||
NonStrictTypeChecker typeChecker{NotNull{&module->internalTypes}, builtinTypes, ice, unifierState, dfg, limits, module};
|
||||
NonStrictTypeChecker typeChecker{NotNull{&module->internalTypes}, builtinTypes, typeFunctionRuntime, ice, unifierState, dfg, limits, module};
|
||||
typeChecker.visit(sourceModule.root);
|
||||
unfreeze(module->interfaceTypes);
|
||||
copyErrors(module->errors, module->interfaceTypes, builtinTypes);
|
||||
|
@ -16,8 +16,6 @@
|
||||
#include "Luau/Unifier.h"
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauCheckNormalizeInvariant, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauNormalizeAwayUninhabitableTables, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauNormalizeNotUnknownIntersection, false);
|
||||
LUAU_FASTFLAGVARIABLE(LuauFixReduceStackPressure, false);
|
||||
LUAU_FASTFLAGVARIABLE(LuauFixCyclicTablesBlowingStack, false);
|
||||
|
||||
@ -40,12 +38,6 @@ static bool fixCyclicTablesBlowingStack()
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
// helper to make `FFlag::LuauNormalizeAwayUninhabitableTables` not explicitly required when DCR is enabled.
|
||||
static bool normalizeAwayUninhabitableTables()
|
||||
{
|
||||
return FFlag::LuauNormalizeAwayUninhabitableTables || FFlag::LuauSolverV2;
|
||||
}
|
||||
|
||||
static bool shouldEarlyExit(NormalizationResult res)
|
||||
{
|
||||
// if res is hit limits, return control flow
|
||||
@ -1621,7 +1613,7 @@ void Normalizer::unionTablesWithTable(TypeIds& heres, TypeId there)
|
||||
// TODO: remove unions of tables where possible
|
||||
|
||||
// we can always skip `never`
|
||||
if (normalizeAwayUninhabitableTables() && get<NeverType>(there))
|
||||
if (get<NeverType>(there))
|
||||
return;
|
||||
|
||||
heres.insert(there);
|
||||
@ -2619,13 +2611,12 @@ std::optional<TypeId> Normalizer::intersectionOfTables(TypeId here, TypeId there
|
||||
seenSet.erase(*tprop.readTy);
|
||||
}
|
||||
|
||||
if (normalizeAwayUninhabitableTables() && NormalizationResult::True != res)
|
||||
if (NormalizationResult::True != res)
|
||||
return {builtinTypes->neverType};
|
||||
}
|
||||
else
|
||||
{
|
||||
if (normalizeAwayUninhabitableTables() &&
|
||||
NormalizationResult::False == isIntersectionInhabited(*hprop.readTy, *tprop.readTy))
|
||||
if (NormalizationResult::False == isIntersectionInhabited(*hprop.readTy, *tprop.readTy))
|
||||
return {builtinTypes->neverType};
|
||||
}
|
||||
|
||||
@ -3258,7 +3249,7 @@ NormalizationResult Normalizer::intersectNormalWithTy(NormalizedType& here, Type
|
||||
// this is a noop since an intersection with `unknown` is trivial.
|
||||
return NormalizationResult::True;
|
||||
}
|
||||
else if ((FFlag::LuauNormalizeNotUnknownIntersection || FFlag::LuauSolverV2) && get<UnknownType>(t))
|
||||
else if (get<UnknownType>(t))
|
||||
{
|
||||
// if we're intersecting with `~unknown`, this is equivalent to intersecting with `never`
|
||||
// this means we should clear the type entirely.
|
||||
@ -3434,7 +3425,10 @@ bool isSubtype(TypeId subTy, TypeId superTy, NotNull<Scope> scope, NotNull<Built
|
||||
UnifierSharedState sharedState{&ice};
|
||||
TypeArena arena;
|
||||
Normalizer normalizer{&arena, builtinTypes, NotNull{&sharedState}};
|
||||
TypeFunctionRuntime typeFunctionRuntime; // TODO: maybe subtyping checks should not invoke user-defined type function runtime
|
||||
TypeCheckLimits limits;
|
||||
TypeFunctionRuntime typeFunctionRuntime{
|
||||
NotNull{&ice}, NotNull{&limits}
|
||||
}; // TODO: maybe subtyping checks should not invoke user-defined type function runtime
|
||||
|
||||
// Subtyping under DCR is not implemented using unification!
|
||||
if (FFlag::LuauSolverV2)
|
||||
@ -3457,7 +3451,10 @@ bool isSubtype(TypePackId subPack, TypePackId superPack, NotNull<Scope> scope, N
|
||||
UnifierSharedState sharedState{&ice};
|
||||
TypeArena arena;
|
||||
Normalizer normalizer{&arena, builtinTypes, NotNull{&sharedState}};
|
||||
TypeFunctionRuntime typeFunctionRuntime; // TODO: maybe subtyping checks should not invoke user-defined type function runtime
|
||||
TypeCheckLimits limits;
|
||||
TypeFunctionRuntime typeFunctionRuntime{
|
||||
NotNull{&ice}, NotNull{&limits}
|
||||
}; // TODO: maybe subtyping checks should not invoke user-defined type function runtime
|
||||
|
||||
// Subtyping under DCR is not implemented using unification!
|
||||
if (FFlag::LuauSolverV2)
|
||||
|
@ -127,7 +127,7 @@ static TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log, bool a
|
||||
return dest.addType(NegationType{a.ty});
|
||||
else if constexpr (std::is_same_v<T, TypeFunctionInstanceType>)
|
||||
{
|
||||
TypeFunctionInstanceType clone{a.function, a.typeArguments, a.packArguments, a.userFuncName, a.userFuncBody};
|
||||
TypeFunctionInstanceType clone{a.function, a.typeArguments, a.packArguments, a.userFuncName};
|
||||
return dest.addType(std::move(clone));
|
||||
}
|
||||
else
|
||||
|
@ -1455,8 +1455,17 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Tabl
|
||||
|
||||
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const MetatableType* subMt, const MetatableType* superMt, NotNull<Scope> scope)
|
||||
{
|
||||
return isCovariantWith(env, subMt->table, superMt->table, scope)
|
||||
.andAlso(isCovariantWith(env, subMt->metatable, superMt->metatable, scope).withBothComponent(TypePath::TypeField::Metatable));
|
||||
if (DFInt::LuauTypeSolverRelease >= 646)
|
||||
{
|
||||
return isCovariantWith(env, subMt->table, superMt->table, scope)
|
||||
.withBothComponent(TypePath::TypeField::Table)
|
||||
.andAlso(isCovariantWith(env, subMt->metatable, superMt->metatable, scope).withBothComponent(TypePath::TypeField::Metatable));
|
||||
}
|
||||
else
|
||||
{
|
||||
return isCovariantWith(env, subMt->table, superMt->table, scope)
|
||||
.andAlso(isCovariantWith(env, subMt->metatable, superMt->metatable, scope).withBothComponent(TypePath::TypeField::Metatable));
|
||||
}
|
||||
}
|
||||
|
||||
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const MetatableType* subMt, const TableType* superTable, NotNull<Scope> scope)
|
||||
|
@ -31,7 +31,7 @@
|
||||
#include <ostream>
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauMagicTypes)
|
||||
LUAU_FASTFLAG(LuauUserDefinedTypeFunctions)
|
||||
LUAU_FASTFLAG(LuauUserDefinedTypeFunctions2)
|
||||
LUAU_DYNAMIC_FASTINT(LuauTypeSolverRelease)
|
||||
|
||||
namespace Luau
|
||||
@ -268,6 +268,7 @@ struct InternalTypeFunctionFinder : TypeOnceVisitor
|
||||
|
||||
void check(
|
||||
NotNull<BuiltinTypes> builtinTypes,
|
||||
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
|
||||
NotNull<UnifierSharedState> unifierState,
|
||||
NotNull<TypeCheckLimits> limits,
|
||||
DcrLogger* logger,
|
||||
@ -277,7 +278,7 @@ void check(
|
||||
{
|
||||
LUAU_TIMETRACE_SCOPE("check", "Typechecking");
|
||||
|
||||
TypeChecker2 typeChecker{builtinTypes, unifierState, limits, logger, &sourceModule, module};
|
||||
TypeChecker2 typeChecker{builtinTypes, typeFunctionRuntime, unifierState, limits, logger, &sourceModule, module};
|
||||
|
||||
typeChecker.visit(sourceModule.root);
|
||||
|
||||
@ -294,6 +295,7 @@ void check(
|
||||
|
||||
TypeChecker2::TypeChecker2(
|
||||
NotNull<BuiltinTypes> builtinTypes,
|
||||
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
|
||||
NotNull<UnifierSharedState> unifierState,
|
||||
NotNull<TypeCheckLimits> limits,
|
||||
DcrLogger* logger,
|
||||
@ -301,13 +303,14 @@ TypeChecker2::TypeChecker2(
|
||||
Module* module
|
||||
)
|
||||
: builtinTypes(builtinTypes)
|
||||
, typeFunctionRuntime(typeFunctionRuntime)
|
||||
, logger(logger)
|
||||
, limits(limits)
|
||||
, ice(unifierState->iceHandler)
|
||||
, sourceModule(sourceModule)
|
||||
, module(module)
|
||||
, normalizer{&module->internalTypes, builtinTypes, unifierState, /* cacheInhabitance */ true}
|
||||
, _subtyping{builtinTypes, NotNull{&module->internalTypes}, NotNull{&normalizer}, NotNull{&typeFunctionRuntime}, NotNull{unifierState->iceHandler}}
|
||||
, _subtyping{builtinTypes, NotNull{&module->internalTypes}, NotNull{&normalizer}, typeFunctionRuntime, NotNull{unifierState->iceHandler}}
|
||||
, subtyping(&_subtyping)
|
||||
{
|
||||
}
|
||||
@ -489,9 +492,7 @@ TypeId TypeChecker2::checkForTypeFunctionInhabitance(TypeId instance, Location l
|
||||
reduceTypeFunctions(
|
||||
instance,
|
||||
location,
|
||||
TypeFunctionContext{
|
||||
NotNull{&module->internalTypes}, builtinTypes, stack.back(), NotNull{&normalizer}, NotNull{&typeFunctionRuntime}, ice, limits
|
||||
},
|
||||
TypeFunctionContext{NotNull{&module->internalTypes}, builtinTypes, stack.back(), NotNull{&normalizer}, typeFunctionRuntime, ice, limits},
|
||||
true
|
||||
)
|
||||
.errors;
|
||||
@ -1198,7 +1199,7 @@ void TypeChecker2::visit(AstStatTypeAlias* stat)
|
||||
void TypeChecker2::visit(AstStatTypeFunction* stat)
|
||||
{
|
||||
// TODO: add type checking for user-defined type functions
|
||||
if (!FFlag::LuauUserDefinedTypeFunctions)
|
||||
if (!FFlag::LuauUserDefinedTypeFunctions2)
|
||||
reportError(TypeError{stat->location, GenericError{"This syntax is not supported"}});
|
||||
}
|
||||
|
||||
@ -1450,7 +1451,7 @@ void TypeChecker2::visitCall(AstExprCall* call)
|
||||
builtinTypes,
|
||||
NotNull{&module->internalTypes},
|
||||
NotNull{&normalizer},
|
||||
NotNull{&typeFunctionRuntime},
|
||||
typeFunctionRuntime,
|
||||
NotNull{stack.back()},
|
||||
ice,
|
||||
limits,
|
||||
|
@ -46,7 +46,8 @@ LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFamilyApplicationCartesianProductLimit, 5'0
|
||||
LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFamilyUseGuesserDepth, -1);
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauLogTypeFamilies, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauUserDefinedTypeFunctions, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauUserDefinedTypeFunctions2, false)
|
||||
LUAU_FASTFLAG(LuauUserDefinedTypeFunctionNoEvaluation)
|
||||
|
||||
LUAU_DYNAMIC_FASTINT(LuauTypeSolverRelease)
|
||||
|
||||
@ -375,7 +376,6 @@ struct TypeFunctionReducer
|
||||
return;
|
||||
|
||||
ctx.userFuncName = tfit->userFuncName;
|
||||
ctx.userFuncBody = tfit->userFuncBody;
|
||||
|
||||
TypeFunctionReductionResult<TypeId> result = tfit->function->reducer(subject, tfit->typeArguments, tfit->packArguments, NotNull{&ctx});
|
||||
handleTypeFunctionReduction(subject, result);
|
||||
@ -416,6 +416,20 @@ struct TypeFunctionReducer
|
||||
}
|
||||
};
|
||||
|
||||
struct LuauTempThreadPopper
|
||||
{
|
||||
explicit LuauTempThreadPopper(lua_State* L)
|
||||
: L(L)
|
||||
{
|
||||
}
|
||||
~LuauTempThreadPopper()
|
||||
{
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
|
||||
lua_State* L = nullptr;
|
||||
};
|
||||
|
||||
static FunctionGraphReductionResult reduceFunctionsInternal(
|
||||
VecDeque<TypeId> queuedTys,
|
||||
VecDeque<TypePackId> queuedTps,
|
||||
@ -586,8 +600,6 @@ static std::optional<TypeFunctionReductionResult<TypeId>> tryDistributeTypeFunct
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
using StateRef = std::unique_ptr<lua_State, void (*)(lua_State*)>;
|
||||
|
||||
TypeFunctionReductionResult<TypeId> userDefinedTypeFunction(
|
||||
TypeId instance,
|
||||
const std::vector<TypeId>& typeParams,
|
||||
@ -595,12 +607,19 @@ TypeFunctionReductionResult<TypeId> userDefinedTypeFunction(
|
||||
NotNull<TypeFunctionContext> ctx
|
||||
)
|
||||
{
|
||||
if (!ctx->userFuncName || !ctx->userFuncBody)
|
||||
if (!ctx->userFuncName)
|
||||
{
|
||||
ctx->ice->ice("all user-defined type functions must have an associated function definition");
|
||||
return {std::nullopt, true, {}, {}};
|
||||
}
|
||||
|
||||
if (FFlag::LuauUserDefinedTypeFunctionNoEvaluation)
|
||||
{
|
||||
// If type functions cannot be evaluated because of errors in the code, we do not generate any additional ones
|
||||
if (!ctx->typeFunctionRuntime->allowEvaluation)
|
||||
return {ctx->builtins->errorRecoveryType(), false, {}, {}};
|
||||
}
|
||||
|
||||
for (auto typeParam : typeParams)
|
||||
{
|
||||
TypeId ty = follow(typeParam);
|
||||
@ -611,62 +630,18 @@ TypeFunctionReductionResult<TypeId> userDefinedTypeFunction(
|
||||
}
|
||||
|
||||
AstName name = *ctx->userFuncName;
|
||||
AstExprFunction* function = *ctx->userFuncBody;
|
||||
|
||||
// Construct ParseResult containing the type function
|
||||
Allocator allocator;
|
||||
AstNameTable names(allocator);
|
||||
lua_State* global = ctx->typeFunctionRuntime->state.get();
|
||||
|
||||
AstExprGlobal globalName{Location{}, name};
|
||||
AstStatFunction typeFunction{Location{}, &globalName, function};
|
||||
AstStat* stmtArray[] = {&typeFunction};
|
||||
AstArray<AstStat*> stmts{stmtArray, 1};
|
||||
AstStatBlock exec{Location{}, stmts};
|
||||
ParseResult parseResult{&exec, 1};
|
||||
if (global == nullptr)
|
||||
return {std::nullopt, true, {}, {}, format("'%s' type function: cannot be evaluated in this context", name.value)};
|
||||
|
||||
BytecodeBuilder builder;
|
||||
try
|
||||
{
|
||||
compileOrThrow(builder, parseResult, names);
|
||||
}
|
||||
catch (CompileError& e)
|
||||
{
|
||||
std::string errMsg = format("'%s' type function failed to compile with error message: %s", name.value, e.what());
|
||||
return {std::nullopt, true, {}, {}, errMsg};
|
||||
}
|
||||
// Separate sandboxed thread for individual execution and private globals
|
||||
lua_State* L = lua_newthread(global);
|
||||
LuauTempThreadPopper popper(global);
|
||||
|
||||
std::string bytecode = builder.getBytecode();
|
||||
|
||||
// Initialize Lua state
|
||||
StateRef globalState(lua_newstate(typeFunctionAlloc, nullptr), lua_close);
|
||||
lua_State* L = globalState.get();
|
||||
|
||||
lua_setthreaddata(L, ctx.get());
|
||||
|
||||
setTypeFunctionEnvironment(L);
|
||||
|
||||
// Register type userdata
|
||||
registerTypeUserData(L);
|
||||
|
||||
luaL_sandbox(L);
|
||||
luaL_sandboxthread(L);
|
||||
|
||||
// Load bytecode into Luau state
|
||||
if (auto error = checkResultForError(L, name.value, luau_load(L, name.value, bytecode.data(), bytecode.size(), 0)))
|
||||
return {std::nullopt, true, {}, {}, error};
|
||||
|
||||
// Execute the loaded chunk to register the function in the global environment
|
||||
if (auto error = checkResultForError(L, name.value, lua_pcall(L, 0, 0, 0)))
|
||||
return {std::nullopt, true, {}, {}, error};
|
||||
|
||||
// Get type function from the global environment
|
||||
lua_getglobal(L, name.value);
|
||||
if (!lua_isfunction(L, -1))
|
||||
{
|
||||
std::string errMsg = format("Could not find '%s' type function in the global scope", name.value);
|
||||
|
||||
return {std::nullopt, true, {}, {}, errMsg};
|
||||
}
|
||||
lua_getglobal(global, name.value);
|
||||
lua_xmove(global, L, 1);
|
||||
|
||||
// Push serialized arguments onto the stack
|
||||
|
||||
@ -690,15 +665,15 @@ TypeFunctionReductionResult<TypeId> userDefinedTypeFunction(
|
||||
// Set up an interrupt handler for type functions to respect type checking limits and LSP cancellation requests.
|
||||
lua_callbacks(L)->interrupt = [](lua_State* L, int gc)
|
||||
{
|
||||
auto ctx = static_cast<const TypeFunctionContext*>(lua_getthreaddata(lua_mainthread(L)));
|
||||
auto ctx = static_cast<const TypeFunctionRuntime*>(lua_getthreaddata(lua_mainthread(L)));
|
||||
if (ctx->limits->finishTime && TimeTrace::getClock() > *ctx->limits->finishTime)
|
||||
ctx->solver->throwTimeLimitError();
|
||||
throw TimeLimitError(ctx->ice->moduleName);
|
||||
|
||||
if (ctx->limits->cancellationToken && ctx->limits->cancellationToken->requested())
|
||||
ctx->solver->throwUserCancelError();
|
||||
throw UserCancelError(ctx->ice->moduleName);
|
||||
};
|
||||
|
||||
if (auto error = checkResultForError(L, name.value, lua_resume(L, nullptr, int(typeParams.size()))))
|
||||
if (auto error = checkResultForError(L, name.value, lua_pcall(L, int(typeParams.size()), 1, 0)))
|
||||
return {std::nullopt, true, {}, {}, error};
|
||||
|
||||
// If the return value is not a type userdata, return with error message
|
||||
@ -796,7 +771,8 @@ TypeFunctionReductionResult<TypeId> lenTypeFunction(
|
||||
return {ctx->builtins->numberType, false, {}, {}};
|
||||
|
||||
// we use the normalized operand here in case there was an intersection or union.
|
||||
TypeId normalizedOperand = ctx->normalizer->typeFromNormal(*normTy);
|
||||
TypeId normalizedOperand =
|
||||
DFInt::LuauTypeSolverRelease >= 646 ? follow(ctx->normalizer->typeFromNormal(*normTy)) : ctx->normalizer->typeFromNormal(*normTy);
|
||||
if (normTy->hasTopTable() || get<TableType>(normalizedOperand))
|
||||
return {ctx->builtins->numberType, false, {}, {}};
|
||||
|
||||
@ -947,6 +923,108 @@ TypeFunctionReductionResult<TypeId> unmTypeFunction(
|
||||
return {std::nullopt, true, {}, {}};
|
||||
}
|
||||
|
||||
void dummyStateClose(lua_State*) {}
|
||||
|
||||
TypeFunctionRuntime::TypeFunctionRuntime(NotNull<InternalErrorReporter> ice, NotNull<TypeCheckLimits> limits)
|
||||
: ice(ice)
|
||||
, limits(limits)
|
||||
, state(nullptr, dummyStateClose)
|
||||
{
|
||||
}
|
||||
|
||||
TypeFunctionRuntime::~TypeFunctionRuntime() {}
|
||||
|
||||
std::optional<std::string> TypeFunctionRuntime::registerFunction(AstStatTypeFunction* function)
|
||||
{
|
||||
if (FFlag::LuauUserDefinedTypeFunctionNoEvaluation)
|
||||
{
|
||||
// If evaluation is disabled, we do not generate additional error messages
|
||||
if (!allowEvaluation)
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
prepareState();
|
||||
|
||||
AstName name = function->name;
|
||||
|
||||
// Construct ParseResult containing the type function
|
||||
Allocator allocator;
|
||||
AstNameTable names(allocator);
|
||||
|
||||
AstExpr* exprFunction = function->body;
|
||||
AstArray<AstExpr*> exprReturns{&exprFunction, 1};
|
||||
AstStatReturn stmtReturn{Location{}, exprReturns};
|
||||
AstStat* stmtArray[] = {&stmtReturn};
|
||||
AstArray<AstStat*> stmts{stmtArray, 1};
|
||||
AstStatBlock exec{Location{}, stmts};
|
||||
ParseResult parseResult{&exec, 1};
|
||||
|
||||
BytecodeBuilder builder;
|
||||
try
|
||||
{
|
||||
compileOrThrow(builder, parseResult, names);
|
||||
}
|
||||
catch (CompileError& e)
|
||||
{
|
||||
return format("'%s' type function failed to compile with error message: %s", name.value, e.what());
|
||||
}
|
||||
|
||||
std::string bytecode = builder.getBytecode();
|
||||
|
||||
lua_State* global = state.get();
|
||||
|
||||
// Separate sandboxed thread for individual execution and private globals
|
||||
lua_State* L = lua_newthread(global);
|
||||
LuauTempThreadPopper popper(global);
|
||||
|
||||
// Create individual environment for the type function
|
||||
luaL_sandboxthread(L);
|
||||
|
||||
// Do not allow global writes to that environment
|
||||
lua_pushvalue(L, LUA_GLOBALSINDEX);
|
||||
lua_setreadonly(L, -1, true);
|
||||
lua_pop(L, 1);
|
||||
|
||||
// Load bytecode into Luau state
|
||||
if (auto error = checkResultForError(L, name.value, luau_load(L, name.value, bytecode.data(), bytecode.size(), 0)))
|
||||
return error;
|
||||
|
||||
// Execute the global function which should return our user-defined type function
|
||||
if (auto error = checkResultForError(L, name.value, lua_resume(L, nullptr, 0)))
|
||||
return error;
|
||||
|
||||
if (!lua_isfunction(L, -1))
|
||||
{
|
||||
lua_pop(L, 1);
|
||||
return format("Could not find '%s' type function in the global scope", name.value);
|
||||
}
|
||||
|
||||
// Store resulting function in the global environment
|
||||
lua_xmove(L, global, 1);
|
||||
lua_setglobal(global, name.value);
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void TypeFunctionRuntime::prepareState()
|
||||
{
|
||||
if (state)
|
||||
return;
|
||||
|
||||
state = StateRef(lua_newstate(typeFunctionAlloc, nullptr), lua_close);
|
||||
lua_State* L = state.get();
|
||||
|
||||
lua_setthreaddata(L, this);
|
||||
|
||||
setTypeFunctionEnvironment(L);
|
||||
|
||||
// Register type userdata
|
||||
registerTypeUserData(L);
|
||||
|
||||
luaL_sandbox(L);
|
||||
luaL_sandboxthread(L);
|
||||
}
|
||||
|
||||
TypeFunctionContext::TypeFunctionContext(NotNull<ConstraintSolver> cs, NotNull<Scope> scope, NotNull<const Constraint> constraint)
|
||||
: arena(cs->arena)
|
||||
, builtins(cs->builtinTypes)
|
||||
|
@ -13,8 +13,8 @@
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
// defined in TypeFunctionRuntimeBuilder.cpp
|
||||
LUAU_DYNAMIC_FASTINT(LuauTypeFunctionSerdeIterationLimit);
|
||||
LUAU_DYNAMIC_FASTINT(LuauTypeFunctionSerdeIterationLimit)
|
||||
LUAU_DYNAMIC_FASTINT(LuauTypeSolverRelease)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -63,21 +63,21 @@ std::optional<std::string> checkResultForError(lua_State* L, const char* typeFun
|
||||
}
|
||||
}
|
||||
|
||||
static const TypeFunctionContext* getTypeFunctionContext(lua_State* L)
|
||||
static TypeFunctionRuntime* getTypeFunctionRuntime(lua_State* L)
|
||||
{
|
||||
return static_cast<const TypeFunctionContext*>(lua_getthreaddata(lua_mainthread(L)));
|
||||
return static_cast<TypeFunctionRuntime*>(lua_getthreaddata(lua_mainthread(L)));
|
||||
}
|
||||
|
||||
TypeFunctionType* allocateTypeFunctionType(lua_State* L, TypeFunctionTypeVariant type)
|
||||
{
|
||||
auto ctx = getTypeFunctionContext(L);
|
||||
return ctx->typeFunctionRuntime->typeArena.allocate(std::move(type));
|
||||
auto ctx = getTypeFunctionRuntime(L);
|
||||
return ctx->typeArena.allocate(std::move(type));
|
||||
}
|
||||
|
||||
TypeFunctionTypePackVar* allocateTypeFunctionTypePack(lua_State* L, TypeFunctionTypePackVariant type)
|
||||
{
|
||||
auto ctx = getTypeFunctionContext(L);
|
||||
return ctx->typeFunctionRuntime->typePackArena.allocate(std::move(type));
|
||||
auto ctx = getTypeFunctionRuntime(L);
|
||||
return ctx->typePackArena.allocate(std::move(type));
|
||||
}
|
||||
|
||||
// Pushes a new type userdata onto the stack
|
||||
@ -678,7 +678,7 @@ static int writeTableProp(lua_State* L)
|
||||
}
|
||||
|
||||
// Luau: `self:setindexer(key: type, value: type)`
|
||||
// Sets the indexer of the table
|
||||
// Sets the indexer of the table, if the key type is `never`, the indexer is removed
|
||||
static int setTableIndexer(lua_State* L)
|
||||
{
|
||||
int argumentCount = lua_gettop(L);
|
||||
@ -693,8 +693,16 @@ static int setTableIndexer(lua_State* L)
|
||||
TypeFunctionTypeId key = getTypeUserData(L, 2);
|
||||
TypeFunctionTypeId value = getTypeUserData(L, 3);
|
||||
|
||||
tftt->indexer = TypeFunctionTableIndexer{key, value};
|
||||
if (DFInt::LuauTypeSolverRelease >= 646)
|
||||
{
|
||||
if (auto tfnt = get<TypeFunctionNeverType>(key))
|
||||
{
|
||||
tftt->indexer = std::nullopt;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
tftt->indexer = TypeFunctionTableIndexer{key, value};
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -1353,7 +1361,7 @@ static int deepCopy(lua_State* L)
|
||||
|
||||
TypeFunctionTypeId arg = getTypeUserData(L, 1);
|
||||
|
||||
TypeFunctionTypeId copy = deepClone(getTypeFunctionContext(L)->typeFunctionRuntime, arg);
|
||||
TypeFunctionTypeId copy = deepClone(NotNull{getTypeFunctionRuntime(L)}, arg);
|
||||
allocTypeUserData(L, copy->type);
|
||||
return 1;
|
||||
}
|
||||
|
@ -32,7 +32,6 @@ LUAU_FASTINTVARIABLE(LuauVisitRecursionLimit, 500)
|
||||
LUAU_FASTFLAG(LuauKnowsTheDataModel3)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false)
|
||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||
LUAU_FASTFLAGVARIABLE(LuauRemoveBadRelationalOperatorWarning, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauAcceptIndexingTableUnionsIntersections, false)
|
||||
|
||||
namespace Luau
|
||||
@ -2794,35 +2793,20 @@ TypeId TypeChecker::checkRelationalOperation(
|
||||
{
|
||||
reportErrors(state.errors);
|
||||
|
||||
if (FFlag::LuauRemoveBadRelationalOperatorWarning)
|
||||
// The original version of this check also produced this error when we had a union type.
|
||||
// However, the old solver does not readily have the ability to discern if the union is comparable.
|
||||
// This is the case when the lhs is e.g. a union of singletons and the rhs is the combined type.
|
||||
// The new solver has much more powerful logic for resolving relational operators, but for now,
|
||||
// we need to be conservative in the old solver to deliver a reasonable developer experience.
|
||||
if (!isEquality && state.errors.empty() && isBoolean(leftType))
|
||||
{
|
||||
// The original version of this check also produced this error when we had a union type.
|
||||
// However, the old solver does not readily have the ability to discern if the union is comparable.
|
||||
// This is the case when the lhs is e.g. a union of singletons and the rhs is the combined type.
|
||||
// The new solver has much more powerful logic for resolving relational operators, but for now,
|
||||
// we need to be conservative in the old solver to deliver a reasonable developer experience.
|
||||
if (!isEquality && state.errors.empty() && isBoolean(leftType))
|
||||
{
|
||||
reportError(
|
||||
expr.location,
|
||||
GenericError{
|
||||
format("Type '%s' cannot be compared with relational operator %s", toString(leftType).c_str(), toString(expr.op).c_str())
|
||||
}
|
||||
reportError(
|
||||
expr.location,
|
||||
GenericError{
|
||||
format("Type '%s' cannot be compared with relational operator %s", toString(leftType).c_str(), toString(expr.op).c_str())
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!isEquality && state.errors.empty() && (get<UnionType>(leftType) || isBoolean(leftType)))
|
||||
{
|
||||
reportError(
|
||||
expr.location,
|
||||
GenericError{
|
||||
format("Type '%s' cannot be compared with relational operator %s", toString(leftType).c_str(), toString(expr.op).c_str())
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return booleanType;
|
||||
}
|
||||
@ -6408,7 +6392,7 @@ void TypeChecker::resolve(const TypeGuardPredicate& typeguardP, RefinementMap& r
|
||||
}
|
||||
|
||||
// We're only interested in the root class of any classes.
|
||||
if (auto ctv = get<ClassType>(type); !ctv || ctv->parent != builtinTypes->classType)
|
||||
if (auto ctv = get<ClassType>(type); !ctv || (ctv->parent != builtinTypes->classType && !hasTag(type, kTypeofRootTag)))
|
||||
return addRefinement(refis, typeguardP.lvalue, errorRecoveryType(scope));
|
||||
|
||||
// This probably hints at breaking out type filtering functions from the predicate solver so that typeof is not tightly coupled with IsA.
|
||||
|
@ -415,6 +415,14 @@ struct TraversalState
|
||||
|
||||
switch (field)
|
||||
{
|
||||
case TypePath::TypeField::Table:
|
||||
if (auto mt = get<MetatableType>(current))
|
||||
{
|
||||
updateCurrent(mt->table);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case TypePath::TypeField::Metatable:
|
||||
if (auto currentType = get<TypeId>(current))
|
||||
{
|
||||
@ -561,6 +569,9 @@ std::string toString(const TypePath::Path& path, bool prefixDot)
|
||||
|
||||
switch (c)
|
||||
{
|
||||
case TypePath::TypeField::Table:
|
||||
result << "table";
|
||||
break;
|
||||
case TypePath::TypeField::Metatable:
|
||||
result << "metatable";
|
||||
break;
|
||||
|
@ -21,7 +21,6 @@ LUAU_FASTFLAGVARIABLE(LuauInstantiateInSubtyping, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTransitiveSubtyping, false)
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAGVARIABLE(LuauFixIndexerSubtypingOrdering, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauUnifierShouldNotCopyError, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauUnifierRecursionOnRestart, false)
|
||||
|
||||
namespace Luau
|
||||
@ -2974,10 +2973,7 @@ bool Unifier::occursCheck(TypePackId needle, TypePackId haystack, bool reversed)
|
||||
if (occurs)
|
||||
{
|
||||
reportError(location, OccursCheckFailed{});
|
||||
if (FFlag::LuauUnifierShouldNotCopyError)
|
||||
log.replace(needle, BoundTypePack{builtinTypes->errorRecoveryTypePack()});
|
||||
else
|
||||
log.replace(needle, *builtinTypes->errorRecoveryTypePack());
|
||||
log.replace(needle, BoundTypePack{builtinTypes->errorRecoveryTypePack()});
|
||||
}
|
||||
|
||||
return occurs;
|
||||
|
@ -146,7 +146,7 @@ private:
|
||||
AstStat* parseTypeAlias(const Location& start, bool exported);
|
||||
|
||||
// type function Name ... end
|
||||
AstStat* parseTypeFunction(const Location& start);
|
||||
AstStat* parseTypeFunction(const Location& start, bool exported);
|
||||
|
||||
AstDeclaredClassProp parseDeclaredClassMethod();
|
||||
|
||||
@ -423,6 +423,7 @@ private:
|
||||
MatchLexeme endMismatchSuspect;
|
||||
|
||||
std::vector<Function> functionStack;
|
||||
size_t typeFunctionDepth = 0;
|
||||
|
||||
DenseHashMap<AstName, AstLocal*> localMap;
|
||||
std::vector<AstLocal*> localStack;
|
||||
|
@ -19,7 +19,7 @@ LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100)
|
||||
LUAU_FASTFLAGVARIABLE(LuauSolverV2, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauNativeAttribute, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauAttributeSyntaxFunExpr, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauUserDefinedTypeFunctionsSyntax, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauUserDefinedTypeFunctionsSyntax2, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauAllowFragmentParsing, false)
|
||||
|
||||
namespace Luau
|
||||
@ -901,10 +901,10 @@ AstStat* Parser::parseReturn()
|
||||
AstStat* Parser::parseTypeAlias(const Location& start, bool exported)
|
||||
{
|
||||
// parsing a type function
|
||||
if (FFlag::LuauUserDefinedTypeFunctionsSyntax)
|
||||
if (FFlag::LuauUserDefinedTypeFunctionsSyntax2)
|
||||
{
|
||||
if (lexer.current().type == Lexeme::ReservedFunction)
|
||||
return parseTypeFunction(start);
|
||||
return parseTypeFunction(start, exported);
|
||||
}
|
||||
|
||||
// parsing a type alias
|
||||
@ -927,11 +927,14 @@ AstStat* Parser::parseTypeAlias(const Location& start, bool exported)
|
||||
}
|
||||
|
||||
// type function Name `(' arglist `)' `=' funcbody `end'
|
||||
AstStat* Parser::parseTypeFunction(const Location& start)
|
||||
AstStat* Parser::parseTypeFunction(const Location& start, bool exported)
|
||||
{
|
||||
Lexeme matchFn = lexer.current();
|
||||
nextLexeme();
|
||||
|
||||
if (exported)
|
||||
report(start, "Type function cannot be exported");
|
||||
|
||||
// parse the name of the type function
|
||||
std::optional<Name> fnName = parseNameOpt("type function name");
|
||||
if (!fnName)
|
||||
@ -939,8 +942,13 @@ AstStat* Parser::parseTypeFunction(const Location& start)
|
||||
|
||||
matchRecoveryStopOnToken[Lexeme::ReservedEnd]++;
|
||||
|
||||
size_t oldTypeFunctionDepth = typeFunctionDepth;
|
||||
typeFunctionDepth = functionStack.size();
|
||||
|
||||
AstExprFunction* body = parseFunctionBody(/* hasself */ false, matchFn, fnName->name, nullptr, AstArray<AstAttr*>({nullptr, 0})).first;
|
||||
|
||||
typeFunctionDepth = oldTypeFunctionDepth;
|
||||
|
||||
matchRecoveryStopOnToken[Lexeme::ReservedEnd]--;
|
||||
|
||||
return allocator.alloc<AstStatTypeFunction>(Location(start, body->location), fnName->name, fnName->location, body);
|
||||
@ -2291,6 +2299,12 @@ AstExpr* Parser::parseNameExpr(const char* context)
|
||||
{
|
||||
AstLocal* local = *value;
|
||||
|
||||
if (FFlag::LuauUserDefinedTypeFunctionsSyntax2)
|
||||
{
|
||||
if (local->functionDepth < typeFunctionDepth)
|
||||
return reportExprError(lexer.current().location, {}, "Type function cannot reference outer local '%s'", local->name.value);
|
||||
}
|
||||
|
||||
return allocator.alloc<AstExprLocal>(name->location, local, local->functionDepth != functionStack.size() - 1);
|
||||
}
|
||||
|
||||
|
@ -57,12 +57,6 @@ bool isAbsolutePath(std::string_view path)
|
||||
#endif
|
||||
}
|
||||
|
||||
bool isExplicitlyRelative(std::string_view path)
|
||||
{
|
||||
return (path == ".") || (path == "..") || (path.size() >= 2 && path[0] == '.' && path[1] == '/') ||
|
||||
(path.size() >= 3 && path[0] == '.' && path[1] == '.' && path[2] == '/');
|
||||
}
|
||||
|
||||
std::optional<std::string> getCurrentWorkingDirectory()
|
||||
{
|
||||
// 2^17 - derived from the Windows path length limit
|
||||
@ -353,6 +347,20 @@ bool traverseDirectory(const std::string& path, const std::function<void(const s
|
||||
}
|
||||
#endif
|
||||
|
||||
bool isFile(const std::string& path)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
DWORD fileAttributes = GetFileAttributesW(fromUtf8(path).c_str());
|
||||
if (fileAttributes == INVALID_FILE_ATTRIBUTES)
|
||||
return false;
|
||||
return (fileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0;
|
||||
#else
|
||||
struct stat st = {};
|
||||
lstat(path.c_str(), &st);
|
||||
return (st.st_mode & S_IFMT) == S_IFREG;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool isDirectory(const std::string& path)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
|
@ -16,7 +16,7 @@ std::optional<std::string> readFile(const std::string& name);
|
||||
std::optional<std::string> readStdin();
|
||||
|
||||
bool isAbsolutePath(std::string_view path);
|
||||
bool isExplicitlyRelative(std::string_view path);
|
||||
bool isFile(const std::string& path);
|
||||
bool isDirectory(const std::string& path);
|
||||
bool traverseDirectory(const std::string& path, const std::function<void(const std::string& name)>& callback);
|
||||
|
||||
|
@ -127,6 +127,8 @@ static int lua_require(lua_State* L)
|
||||
|
||||
if (resolvedRequire.status == RequireResolver::ModuleStatus::Cached)
|
||||
return finishrequire(L);
|
||||
else if (resolvedRequire.status == RequireResolver::ModuleStatus::Ambiguous)
|
||||
luaL_errorL(L, "require path could not be resolved to a unique file");
|
||||
else if (resolvedRequire.status == RequireResolver::ModuleStatus::NotFound)
|
||||
luaL_errorL(L, "error requiring module");
|
||||
|
||||
|
@ -24,6 +24,9 @@ RequireResolver::RequireResolver(lua_State* L, std::string path)
|
||||
|
||||
std::replace(pathToResolve.begin(), pathToResolve.end(), '\\', '/');
|
||||
|
||||
if (!isPrefixValid())
|
||||
luaL_argerrorL(L, 1, "require path must start with a valid prefix: ./, ../, or @");
|
||||
|
||||
substituteAliasIfPresent(pathToResolve);
|
||||
}
|
||||
|
||||
@ -44,44 +47,14 @@ RequireResolver::ModuleStatus RequireResolver::findModule()
|
||||
// Put _MODULES table on stack for checking and saving to the cache
|
||||
luaL_findtable(L, LUA_REGISTRYINDEX, "_MODULES", 1);
|
||||
|
||||
RequireResolver::ModuleStatus moduleStatus = findModuleImpl();
|
||||
|
||||
if (moduleStatus != RequireResolver::ModuleStatus::NotFound)
|
||||
return moduleStatus;
|
||||
|
||||
if (!shouldSearchPathsArray())
|
||||
return moduleStatus;
|
||||
|
||||
if (!isConfigFullyResolved)
|
||||
parseNextConfig();
|
||||
|
||||
// Index-based iteration because std::iterator may be invalidated if config.paths is reallocated
|
||||
for (size_t i = 0; i < config.paths.size(); ++i)
|
||||
{
|
||||
// "placeholder" acts as a requiring file in the relevant directory
|
||||
std::optional<std::string> absolutePathOpt = resolvePath(pathToResolve, joinPaths(config.paths[i], "placeholder"));
|
||||
|
||||
if (!absolutePathOpt)
|
||||
luaL_errorL(L, "error requiring module");
|
||||
|
||||
chunkname = *absolutePathOpt;
|
||||
absolutePath = *absolutePathOpt;
|
||||
|
||||
moduleStatus = findModuleImpl();
|
||||
|
||||
if (moduleStatus != RequireResolver::ModuleStatus::NotFound)
|
||||
return moduleStatus;
|
||||
|
||||
// Before finishing the loop, parse more config files if there are any
|
||||
if (i == config.paths.size() - 1 && !isConfigFullyResolved)
|
||||
parseNextConfig(); // could reallocate config.paths when paths are parsed and added
|
||||
}
|
||||
|
||||
return RequireResolver::ModuleStatus::NotFound;
|
||||
return findModuleImpl();
|
||||
}
|
||||
|
||||
RequireResolver::ModuleStatus RequireResolver::findModuleImpl()
|
||||
{
|
||||
if (isPathAmbiguous(absolutePath))
|
||||
return ModuleStatus::Ambiguous;
|
||||
|
||||
static const std::array<const char*, 4> possibleSuffixes = {".luau", ".lua", "/init.luau", "/init.lua"};
|
||||
|
||||
size_t unsuffixedAbsolutePathSize = absolutePath.size();
|
||||
@ -113,15 +86,34 @@ RequireResolver::ModuleStatus RequireResolver::findModuleImpl()
|
||||
return ModuleStatus::NotFound;
|
||||
}
|
||||
|
||||
bool RequireResolver::isPathAmbiguous(const std::string& path)
|
||||
{
|
||||
bool found = false;
|
||||
for (const char* suffix : {".luau", ".lua"})
|
||||
{
|
||||
if (isFile(path + suffix))
|
||||
{
|
||||
if (found)
|
||||
return true;
|
||||
else
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if (isDirectory(path) && found)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool RequireResolver::isRequireAllowed(std::string_view sourceChunkname)
|
||||
{
|
||||
LUAU_ASSERT(!sourceChunkname.empty());
|
||||
return (sourceChunkname[0] == '=' || sourceChunkname[0] == '@');
|
||||
}
|
||||
|
||||
bool RequireResolver::shouldSearchPathsArray()
|
||||
bool RequireResolver::isPrefixValid()
|
||||
{
|
||||
return !isAbsolutePath(pathToResolve) && !isExplicitlyRelative(pathToResolve);
|
||||
return pathToResolve.compare(0, 2, "./") == 0 || pathToResolve.compare(0, 3, "../") == 0 || pathToResolve.compare(0, 1, "@") == 0;
|
||||
}
|
||||
|
||||
void RequireResolver::resolveAndStoreDefaultPaths()
|
||||
@ -283,24 +275,10 @@ void RequireResolver::parseConfigInDirectory(const std::string& directory)
|
||||
{
|
||||
std::string configPath = joinPaths(directory, Luau::kConfigName);
|
||||
|
||||
size_t numPaths = config.paths.size();
|
||||
|
||||
if (std::optional<std::string> contents = readFile(configPath))
|
||||
{
|
||||
std::optional<std::string> error = Luau::parseConfig(*contents, config);
|
||||
if (error)
|
||||
luaL_errorL(L, "error parsing %s (%s)", configPath.c_str(), (*error).c_str());
|
||||
}
|
||||
|
||||
// Resolve any newly obtained relative paths in "paths" in relation to configPath
|
||||
for (auto it = config.paths.begin() + numPaths; it != config.paths.end(); ++it)
|
||||
{
|
||||
if (!isAbsolutePath(*it))
|
||||
{
|
||||
if (std::optional<std::string> resolvedPath = resolvePath(*it, configPath))
|
||||
*it = std::move(*resolvedPath);
|
||||
else
|
||||
luaL_errorL(L, "error requiring module");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ public:
|
||||
{
|
||||
Cached,
|
||||
FileRead,
|
||||
Ambiguous,
|
||||
NotFound
|
||||
};
|
||||
|
||||
@ -46,10 +47,11 @@ private:
|
||||
bool isConfigFullyResolved = false;
|
||||
|
||||
bool isRequireAllowed(std::string_view sourceChunkname);
|
||||
bool shouldSearchPathsArray();
|
||||
bool isPrefixValid();
|
||||
|
||||
void resolveAndStoreDefaultPaths();
|
||||
ModuleStatus findModuleImpl();
|
||||
bool isPathAmbiguous(const std::string& path);
|
||||
|
||||
std::optional<std::string> getRequiringContextAbsolute();
|
||||
std::string getRequiringContextRelative();
|
||||
|
@ -3634,6 +3634,10 @@ struct Compiler
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
else if (node->is<AstStatTypeFunction>())
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_ASSERT(!"Unknown statement type");
|
||||
|
@ -32,7 +32,6 @@ struct Config
|
||||
|
||||
std::vector<std::string> globals;
|
||||
|
||||
std::vector<std::string> paths;
|
||||
std::unordered_map<std::string, std::string> aliases;
|
||||
};
|
||||
|
||||
|
@ -304,11 +304,6 @@ Error parseConfig(const std::string& contents, Config& config, bool compat)
|
||||
config.globals.push_back(value);
|
||||
return std::nullopt;
|
||||
}
|
||||
else if (keys.size() == 1 && keys[0] == "paths")
|
||||
{
|
||||
config.paths.push_back(value);
|
||||
return std::nullopt;
|
||||
}
|
||||
else if (keys.size() == 2 && keys[0] == "aliases")
|
||||
return parseAlias(config.aliases, keys[1], value);
|
||||
else if (compat && keys.size() == 2 && keys[0] == "language" && keys[1] == "mode")
|
||||
|
@ -8,6 +8,8 @@
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(LuauDocumentationAtPosition)
|
||||
|
||||
struct DocumentationSymbolFixture : BuiltinsFixture
|
||||
{
|
||||
std::optional<DocumentationSymbol> getDocSymbol(const std::string& source, Position position)
|
||||
@ -163,6 +165,44 @@ TEST_CASE_FIXTURE(DocumentationSymbolFixture, "table_overloaded_function_prop")
|
||||
CHECK_EQ(symbol, "@test/global/Foo.new/overload/(string) -> number");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(DocumentationSymbolFixture, "string_metatable_method")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauDocumentationAtPosition, true};
|
||||
std::optional<DocumentationSymbol> symbol = getDocSymbol(
|
||||
R"(
|
||||
local x: string = "Foo"
|
||||
x:rep(2)
|
||||
)",
|
||||
Position(2, 12)
|
||||
);
|
||||
|
||||
CHECK_EQ(symbol, "@luau/global/string.rep");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(DocumentationSymbolFixture, "parent_class_method")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauDocumentationAtPosition, true};
|
||||
loadDefinition(R"(
|
||||
declare class Foo
|
||||
function bar(self, x: string): number
|
||||
end
|
||||
|
||||
declare class Bar extends Foo
|
||||
function notbar(self, x: string): number
|
||||
end
|
||||
)");
|
||||
|
||||
std::optional<DocumentationSymbol> symbol = getDocSymbol(
|
||||
R"(
|
||||
local x: Bar = Bar.new()
|
||||
x:bar("asdf")
|
||||
)",
|
||||
Position(2, 11)
|
||||
);
|
||||
|
||||
CHECK_EQ(symbol, "@test/globaltype/Foo.bar");
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
||||
TEST_SUITE_BEGIN("AstQuery");
|
||||
|
@ -21,6 +21,7 @@ LUAU_FASTINT(LuauCompileInlineThresholdMaxBoost)
|
||||
LUAU_FASTINT(LuauCompileLoopUnrollThreshold)
|
||||
LUAU_FASTINT(LuauCompileLoopUnrollThresholdMaxBoost)
|
||||
LUAU_FASTINT(LuauRecursionLimit)
|
||||
LUAU_FASTFLAG(LuauUserDefinedTypeFunctionsSyntax2)
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
@ -2796,6 +2797,16 @@ TEST_CASE("TypeAliasing")
|
||||
CHECK_NOTHROW(Luau::compileOrThrow(bcb, "type A = number local a: A = 1", options, parseOptions));
|
||||
}
|
||||
|
||||
TEST_CASE("TypeFunction")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
|
||||
|
||||
Luau::BytecodeBuilder bcb;
|
||||
Luau::CompileOptions options;
|
||||
Luau::ParseOptions parseOptions;
|
||||
CHECK_NOTHROW(Luau::compileOrThrow(bcb, "type function a() return types.any end", options, parseOptions));
|
||||
}
|
||||
|
||||
TEST_CASE("DebugLineInfo")
|
||||
{
|
||||
Luau::BytecodeBuilder bcb;
|
||||
|
@ -25,6 +25,7 @@ void ConstraintGeneratorFixture::generateConstraints(const std::string& code)
|
||||
cg = std::make_unique<ConstraintGenerator>(
|
||||
mainModule,
|
||||
NotNull{&normalizer},
|
||||
NotNull{&typeFunctionRuntime},
|
||||
NotNull(&moduleResolver),
|
||||
builtinTypes,
|
||||
NotNull(&ice),
|
||||
|
@ -20,7 +20,8 @@ struct ConstraintGeneratorFixture : Fixture
|
||||
DcrLogger logger;
|
||||
UnifierSharedState sharedState{&ice};
|
||||
Normalizer normalizer{&arena, builtinTypes, NotNull{&sharedState}};
|
||||
TypeFunctionRuntime typeFunctionRuntime;
|
||||
TypeCheckLimits limits;
|
||||
TypeFunctionRuntime typeFunctionRuntime{NotNull{&ice}, NotNull{&limits}};
|
||||
|
||||
std::unique_ptr<DataFlowGraph> dfg;
|
||||
std::unique_ptr<ConstraintGenerator> cg;
|
||||
|
@ -598,6 +598,72 @@ BuiltinsFixture::BuiltinsFixture(bool freeze, bool prepareAutocomplete)
|
||||
Luau::freeze(frontend.globalsForAutocomplete.globalTypes);
|
||||
}
|
||||
|
||||
static std::vector<std::string_view> parsePathExpr(const AstExpr& pathExpr)
|
||||
{
|
||||
const AstExprIndexName* indexName = pathExpr.as<AstExprIndexName>();
|
||||
if (!indexName)
|
||||
return {};
|
||||
|
||||
std::vector<std::string_view> segments{indexName->index.value};
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (AstExprIndexName* in = indexName->expr->as<AstExprIndexName>())
|
||||
{
|
||||
segments.push_back(in->index.value);
|
||||
indexName = in;
|
||||
continue;
|
||||
}
|
||||
else if (AstExprGlobal* indexNameAsGlobal = indexName->expr->as<AstExprGlobal>())
|
||||
{
|
||||
segments.push_back(indexNameAsGlobal->name.value);
|
||||
break;
|
||||
}
|
||||
else if (AstExprLocal* indexNameAsLocal = indexName->expr->as<AstExprLocal>())
|
||||
{
|
||||
segments.push_back(indexNameAsLocal->local->name.value);
|
||||
break;
|
||||
}
|
||||
else
|
||||
return {};
|
||||
}
|
||||
|
||||
std::reverse(segments.begin(), segments.end());
|
||||
return segments;
|
||||
}
|
||||
|
||||
std::optional<std::string> pathExprToModuleName(const ModuleName& currentModuleName, const std::vector<std::string_view>& segments)
|
||||
{
|
||||
if (segments.empty())
|
||||
return std::nullopt;
|
||||
|
||||
std::vector<std::string_view> result;
|
||||
|
||||
auto it = segments.begin();
|
||||
|
||||
if (*it == "script" && !currentModuleName.empty())
|
||||
{
|
||||
result = split(currentModuleName, '/');
|
||||
++it;
|
||||
}
|
||||
|
||||
for (; it != segments.end(); ++it)
|
||||
{
|
||||
if (result.size() > 1 && *it == "Parent")
|
||||
result.pop_back();
|
||||
else
|
||||
result.push_back(*it);
|
||||
}
|
||||
|
||||
return join(result, "/");
|
||||
}
|
||||
|
||||
std::optional<std::string> pathExprToModuleName(const ModuleName& currentModuleName, const AstExpr& pathExpr)
|
||||
{
|
||||
std::vector<std::string_view> segments = parsePathExpr(pathExpr);
|
||||
return pathExprToModuleName(currentModuleName, segments);
|
||||
}
|
||||
|
||||
ModuleName fromString(std::string_view name)
|
||||
{
|
||||
return ModuleName(name);
|
||||
|
@ -20,8 +20,10 @@
|
||||
|
||||
#include "doctest.h"
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -159,6 +161,9 @@ struct BuiltinsFixture : Fixture
|
||||
BuiltinsFixture(bool freeze = true, bool prepareAutocomplete = false);
|
||||
};
|
||||
|
||||
std::optional<std::string> pathExprToModuleName(const ModuleName& currentModuleName, const std::vector<std::string_view>& segments);
|
||||
std::optional<std::string> pathExprToModuleName(const ModuleName& currentModuleName, const AstExpr& pathExpr);
|
||||
|
||||
ModuleName fromString(std::string_view name);
|
||||
|
||||
template<typename T>
|
||||
|
@ -11,7 +11,6 @@
|
||||
#include "Luau/BuiltinDefinitions.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauNormalizeNotUnknownIntersection)
|
||||
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
||||
using namespace Luau;
|
||||
|
||||
@ -970,8 +969,6 @@ TEST_CASE_FIXTURE(NormalizeFixture, "non_final_types_can_be_normalized_but_are_n
|
||||
|
||||
TEST_CASE_FIXTURE(NormalizeFixture, "intersect_with_not_unknown")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauNormalizeNotUnknownIntersection, true};
|
||||
|
||||
TypeId notUnknown = arena.addType(NegationType{builtinTypes->unknownType});
|
||||
TypeId type = arena.addType(IntersectionType{{builtinTypes->numberType, notUnknown}});
|
||||
std::shared_ptr<const NormalizedType> normalized = normalizer.normalize(type);
|
||||
|
@ -17,7 +17,7 @@ LUAU_FASTINT(LuauTypeLengthLimit)
|
||||
LUAU_FASTINT(LuauParseErrorLimit)
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauAttributeSyntaxFunExpr)
|
||||
LUAU_FASTFLAG(LuauUserDefinedTypeFunctionsSyntax)
|
||||
LUAU_FASTFLAG(LuauUserDefinedTypeFunctionsSyntax2)
|
||||
|
||||
namespace
|
||||
{
|
||||
@ -2380,7 +2380,7 @@ TEST_CASE_FIXTURE(Fixture, "invalid_type_forms")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "parse_user_defined_type_functions")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauUserDefinedTypeFunctionsSyntax, true};
|
||||
ScopedFastFlag sff{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
|
||||
|
||||
AstStat* stat = parse(R"(
|
||||
type function foo()
|
||||
@ -2394,6 +2394,38 @@ TEST_CASE_FIXTURE(Fixture, "parse_user_defined_type_functions")
|
||||
REQUIRE(f->name == "foo");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "parse_nested_type_function")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
|
||||
|
||||
AstStat* stat = parse(R"(
|
||||
local v1 = 1
|
||||
type function foo()
|
||||
local v2 = 2
|
||||
local function bar()
|
||||
v2 += 1
|
||||
type function inner() end
|
||||
v2 += 2
|
||||
end
|
||||
local function bar2()
|
||||
v2 += 3
|
||||
end
|
||||
end
|
||||
local function bar() v1 += 1 end
|
||||
)");
|
||||
|
||||
REQUIRE(stat != nullptr);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "invalid_user_defined_type_functions")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
|
||||
|
||||
matchParseError("export type function foo() end", "Type function cannot be exported");
|
||||
matchParseError("local foo = 1; type function bar() print(foo) end", "Type function cannot reference outer local 'foo'");
|
||||
matchParseError("type function foo() local v1 = 1; type function bar() print(v1) end end", "Type function cannot reference outer local 'v1'");
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
||||
TEST_SUITE_BEGIN("ParseErrorRecovery");
|
||||
|
@ -308,6 +308,22 @@ TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireInitLua")
|
||||
assertOutputContainsAll({"true", "result from init.lua"});
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireWithFileAmbiguity")
|
||||
{
|
||||
std::string ambiguousPath = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/ambiguous_file_requirer";
|
||||
|
||||
runProtectedRequire(ambiguousPath);
|
||||
assertOutputContainsAll({"false", "require path could not be resolved to a unique file"});
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireWithDirectoryAmbiguity")
|
||||
{
|
||||
std::string ambiguousPath = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/ambiguous_directory_requirer";
|
||||
|
||||
runProtectedRequire(ambiguousPath);
|
||||
assertOutputContainsAll({"false", "require path could not be resolved to a unique file"});
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ReplWithPathFixture, "CheckCacheAfterRequireLuau")
|
||||
{
|
||||
std::string relativePath = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/module";
|
||||
@ -401,25 +417,11 @@ TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireAbsolutePath")
|
||||
assertOutputContainsAll({"false", "cannot require an absolute path"});
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ReplWithPathFixture, "PathsArrayRelativePath")
|
||||
TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireUnprefixedPath")
|
||||
{
|
||||
std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/with_config/src/requirer";
|
||||
std::string path = "an/unprefixed/path";
|
||||
runProtectedRequire(path);
|
||||
assertOutputContainsAll({"true", "result from library"});
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ReplWithPathFixture, "PathsArrayExplicitlyRelativePath")
|
||||
{
|
||||
std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/with_config/src/fail_requirer";
|
||||
runProtectedRequire(path);
|
||||
assertOutputContainsAll({"false", "error requiring module"});
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ReplWithPathFixture, "PathsArrayFromParent")
|
||||
{
|
||||
std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/with_config/src/global_library_requirer";
|
||||
runProtectedRequire(path);
|
||||
assertOutputContainsAll({"true", "result from global_library"});
|
||||
assertOutputContainsAll({"false", "require path must start with a valid prefix: ./, ../, or @"});
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ReplWithPathFixture, "RequirePathWithAlias")
|
||||
|
@ -66,7 +66,8 @@ struct SubtypeFixture : Fixture
|
||||
InternalErrorReporter iceReporter;
|
||||
UnifierSharedState sharedState{&ice};
|
||||
Normalizer normalizer{&arena, builtinTypes, NotNull{&sharedState}};
|
||||
TypeFunctionRuntime typeFunctionRuntime;
|
||||
TypeCheckLimits limits;
|
||||
TypeFunctionRuntime typeFunctionRuntime{NotNull{&iceReporter}, NotNull{&limits}};
|
||||
|
||||
ScopedFastFlag sff{FFlag::LuauSolverV2, true};
|
||||
|
||||
|
@ -13,7 +13,7 @@ using namespace Luau;
|
||||
LUAU_FASTFLAG(LuauRecursiveTypeParameterRestriction);
|
||||
LUAU_FASTFLAG(LuauSolverV2);
|
||||
LUAU_FASTFLAG(LuauAttributeSyntax);
|
||||
LUAU_FASTFLAG(LuauUserDefinedTypeFunctions)
|
||||
LUAU_FASTFLAG(LuauUserDefinedTypeFunctions2)
|
||||
|
||||
TEST_SUITE_BEGIN("ToString");
|
||||
|
||||
@ -964,12 +964,11 @@ TEST_CASE_FIXTURE(Fixture, "correct_stringification_user_defined_type_functions"
|
||||
std::vector<TypeId>{builtinTypes->numberType}, // Type Function Arguments
|
||||
{},
|
||||
{AstName{"woohoo"}}, // Type Function Name
|
||||
std::nullopt
|
||||
};
|
||||
|
||||
Type tv{tftt};
|
||||
|
||||
if (FFlag::LuauSolverV2 && FFlag::LuauUserDefinedTypeFunctions)
|
||||
if (FFlag::LuauSolverV2 && FFlag::LuauUserDefinedTypeFunctions2)
|
||||
CHECK_EQ(toString(&tv, {}), "woohoo<number>");
|
||||
}
|
||||
|
||||
|
@ -12,7 +12,7 @@
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(LuauUserDefinedTypeFunctionsSyntax)
|
||||
LUAU_FASTFLAG(LuauUserDefinedTypeFunctionsSyntax2)
|
||||
|
||||
TEST_SUITE_BEGIN("TranspilerTests");
|
||||
|
||||
@ -698,7 +698,7 @@ TEST_CASE_FIXTURE(Fixture, "transpile_string_literal_escape")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "transpile_type_functions")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauUserDefinedTypeFunctionsSyntax, true};
|
||||
ScopedFastFlag sff{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
|
||||
|
||||
std::string code = R"( type function foo(arg1, arg2) if arg1 == arg2 then return arg1 end return arg2 end )";
|
||||
|
||||
|
@ -13,7 +13,7 @@
|
||||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauUserDefinedTypeFunctions)
|
||||
LUAU_FASTFLAG(LuauUserDefinedTypeFunctions2)
|
||||
LUAU_DYNAMIC_FASTINT(LuauTypeFamilyApplicationCartesianProductLimit)
|
||||
|
||||
struct TypeFunctionFixture : Fixture
|
||||
@ -1247,4 +1247,20 @@ TEST_CASE_FIXTURE(ClassFixture, "rawget_type_function_errors_w_classes")
|
||||
CHECK(toString(result.errors[0]) == "Property '\"BaseField\"' does not exist on type 'BaseClass'");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "fuzz_len_type_function_follow")
|
||||
{
|
||||
// Should not fail assertions
|
||||
check(R"(
|
||||
local _
|
||||
_ = true
|
||||
for l0=_,_,# _ do
|
||||
end
|
||||
for l0=_,_ do
|
||||
if _ then
|
||||
_ += _
|
||||
end
|
||||
end
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -8,16 +8,17 @@
|
||||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauUserDefinedTypeFunctionsSyntax)
|
||||
LUAU_FASTFLAG(LuauUserDefinedTypeFunctions)
|
||||
LUAU_FASTFLAG(LuauUserDefinedTypeFunctionsSyntax2)
|
||||
LUAU_FASTFLAG(LuauUserDefinedTypeFunctions2)
|
||||
LUAU_FASTFLAG(LuauUserDefinedTypeFunctionNoEvaluation)
|
||||
|
||||
TEST_SUITE_BEGIN("UserDefinedTypeFunctionTests");
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_nil_serialization_works")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type function serialize_nil(arg)
|
||||
@ -33,8 +34,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_nil_serialization_works")
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_nil_methods_work")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type function getnil()
|
||||
@ -54,8 +55,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_nil_methods_work")
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_unknown_serialization_works")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type function serialize_unknown(arg)
|
||||
@ -71,8 +72,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_unknown_serialization_works")
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_unknown_methods_work")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type function getunknown()
|
||||
@ -92,8 +93,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_unknown_methods_work")
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_never_serialization_works")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type function serialize_never(arg)
|
||||
@ -109,8 +110,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_never_serialization_works")
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_never_methods_work")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type function getnever()
|
||||
@ -130,8 +131,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_never_methods_work")
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_any_serialization_works")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type function serialize_any(arg)
|
||||
@ -147,8 +148,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_any_serialization_works")
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_any_methods_work")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type function getany()
|
||||
@ -168,8 +169,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_any_methods_work")
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_boolean_serialization_works")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type function serialize_bool(arg)
|
||||
@ -185,8 +186,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_boolean_serialization_works")
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_boolean_methods_work")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type function getboolean()
|
||||
@ -206,8 +207,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_boolean_methods_work")
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_number_serialization_works")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type function serialize_num(arg)
|
||||
@ -223,8 +224,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_number_serialization_works")
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_number_methods_work")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type function getnumber()
|
||||
@ -244,8 +245,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_number_methods_work")
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_string_serialization_works")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type function serialize_str(arg)
|
||||
@ -261,8 +262,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_string_serialization_works")
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_string_methods_work")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type function getstring()
|
||||
@ -282,8 +283,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_string_methods_work")
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_boolsingleton_serialization_works")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type function serialize_boolsingleton(arg)
|
||||
@ -299,8 +300,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_boolsingleton_serialization_works")
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_boolsingleton_methods_work")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type function getboolsingleton()
|
||||
@ -320,8 +321,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_boolsingleton_methods_work")
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_strsingleton_serialization_works")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type function serialize_strsingleton(arg)
|
||||
@ -337,8 +338,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_strsingleton_serialization_works")
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_strsingleton_methods_work")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type function getstrsingleton()
|
||||
@ -358,8 +359,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_strsingleton_methods_work")
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_union_serialization_works")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type function serialize_union(arg)
|
||||
@ -379,8 +380,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_union_serialization_works")
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_union_methods_work")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type function getunion()
|
||||
@ -409,8 +410,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_union_methods_work")
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_intersection_serialization_works")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type function serialize_intersection(arg)
|
||||
@ -430,8 +431,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_intersection_serialization_works")
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_intersection_methods_work")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type function getintersection()
|
||||
@ -466,8 +467,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_intersection_methods_work")
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_negation_methods_work")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type function getnegation()
|
||||
@ -492,8 +493,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_negation_methods_work")
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_table_serialization_works")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type function serialize_table(arg)
|
||||
@ -513,8 +514,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_table_serialization_works")
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_table_methods_work")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type function gettable()
|
||||
@ -553,8 +554,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_table_methods_work")
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_metatable_methods_work")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type function getmetatable()
|
||||
@ -587,8 +588,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_metatable_methods_work")
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_function_serialization_works")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type function serialize_func(arg)
|
||||
@ -604,8 +605,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_function_serialization_works")
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_function_methods_work")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type function getfunction()
|
||||
@ -635,8 +636,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_function_methods_work")
|
||||
TEST_CASE_FIXTURE(ClassFixture, "udtf_class_serialization_works")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type function serialize_class(arg)
|
||||
@ -651,8 +652,8 @@ TEST_CASE_FIXTURE(ClassFixture, "udtf_class_serialization_works")
|
||||
TEST_CASE_FIXTURE(ClassFixture, "udtf_class_methods_works")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
|
||||
|
||||
|
||||
CheckResult result = check(R"(
|
||||
@ -675,8 +676,8 @@ TEST_CASE_FIXTURE(ClassFixture, "udtf_class_methods_works")
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_check_mutability")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type function checkmut()
|
||||
@ -708,8 +709,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_check_mutability")
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_copy_works")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type function getcopy()
|
||||
@ -742,8 +743,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_copy_works")
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_simple_cyclic_serialization_works")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type function serialize_cycle(arg)
|
||||
@ -764,8 +765,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_simple_cyclic_serialization_works")
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_createtable_bad_metatable")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type function badmetatable()
|
||||
@ -786,8 +787,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_createtable_bad_metatable")
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_complex_cyclic_serialization_works")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type function serialize_cycle2(arg)
|
||||
@ -816,8 +817,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_complex_cyclic_serialization_works")
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_user_error_is_reported")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type function errors_if_string(arg)
|
||||
@ -839,8 +840,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_user_error_is_reported")
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_type_overrides_call_metamethod")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type function hello(arg)
|
||||
@ -858,8 +859,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_type_overrides_call_metamethod")
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_type_overrides_eq_metamethod")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type function hello()
|
||||
@ -884,8 +885,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_type_overrides_eq_metamethod")
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_function_type_cant_call_get_props")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type function hello(arg)
|
||||
@ -903,34 +904,62 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_function_type_cant_call_get_props")
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_cannot_call_other")
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_calling_each_other")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type function foo()
|
||||
return "hi"
|
||||
end
|
||||
local x = true;
|
||||
type function cannot_call_others()
|
||||
return foo()
|
||||
type function bar()
|
||||
return types.singleton(foo())
|
||||
end
|
||||
local function ok(idx: cannot_call_others<>): string return idx end
|
||||
local function ok(idx: bar<>): nil return idx end
|
||||
)");
|
||||
|
||||
LUAU_CHECK_ERROR_COUNT(4, result); // There are 2 type function uninhabited error, 2 user defined type function error
|
||||
UserDefinedTypeFunctionError* e = get<UserDefinedTypeFunctionError>(result.errors[0]);
|
||||
REQUIRE(e);
|
||||
CHECK(e->message == "'cannot_call_others' type function errored at runtime: [string \"cannot_call_others\"]:7: attempt to call a nil value");
|
||||
LUAU_CHECK_ERROR_COUNT(1, result);
|
||||
TypePackMismatch* tpm = get<TypePackMismatch>(result.errors[0]);
|
||||
REQUIRE(tpm);
|
||||
CHECK(toString(tpm->givenTp) == "\"hi\"");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_no_shared_state")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type function foo()
|
||||
if not glob then
|
||||
glob = 'a'
|
||||
else
|
||||
glob ..= 'b'
|
||||
end
|
||||
|
||||
return glob
|
||||
end
|
||||
type function bar(prefix)
|
||||
return types.singleton(prefix:value() .. foo())
|
||||
end
|
||||
local function ok1(idx: bar<'x'>): nil return idx end
|
||||
local function ok2(idx: bar<'y'>): nil return idx end
|
||||
)");
|
||||
|
||||
// We are only checking first errors, others are mostly duplicates
|
||||
LUAU_CHECK_ERROR_COUNT(8, result);
|
||||
CHECK(toString(result.errors[0]) == R"('bar' type function errored at runtime: [string "foo"]:4: attempt to modify a readonly table)");
|
||||
CHECK(toString(result.errors[1]) == R"(Type function instance bar<"x"> is uninhabited)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_optionify")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type function optionify(tbl)
|
||||
@ -959,8 +988,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_optionify")
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_calling_illegal_global")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type function illegal(arg)
|
||||
@ -980,9 +1009,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_calling_illegal_global")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_recursion_and_gc")
|
||||
{
|
||||
ScopedFastFlag newSolver{ FFlag::LuauSolverV2, true };
|
||||
ScopedFastFlag udtfSyntax{ FFlag::LuauUserDefinedTypeFunctionsSyntax, true };
|
||||
ScopedFastFlag udtf{ FFlag::LuauUserDefinedTypeFunctions, true };
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type function foo(tbl)
|
||||
@ -1004,4 +1033,72 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_recursion_and_gc")
|
||||
REQUIRE(tpm);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_recovery_no_upvalues")
|
||||
{
|
||||
ScopedFastFlag solverV2{FFlag::LuauSolverV2, true};
|
||||
ScopedFastFlag userDefinedTypeFunctionsSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
|
||||
ScopedFastFlag userDefinedTypeFunctions{FFlag::LuauUserDefinedTypeFunctions2, true};
|
||||
ScopedFastFlag userDefinedTypeFunctionNoEvaluation{FFlag::LuauUserDefinedTypeFunctionNoEvaluation, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local var
|
||||
|
||||
type function save_upvalue(arg)
|
||||
var = 1
|
||||
return arg
|
||||
end
|
||||
|
||||
type test = "test"
|
||||
local function ok(idx: save_upvalue<test>): "test"
|
||||
return idx
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_CHECK_ERROR_COUNT(1, result);
|
||||
CHECK(toString(result.errors[0]) == R"(Type function cannot reference outer local 'var')");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_follow")
|
||||
{
|
||||
ScopedFastFlag solverV2{FFlag::LuauSolverV2, true};
|
||||
ScopedFastFlag userDefinedTypeFunctionsSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
|
||||
ScopedFastFlag userDefinedTypeFunctions{FFlag::LuauUserDefinedTypeFunctions2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type t0 = any
|
||||
type function t0()
|
||||
return types.any
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_CHECK_ERROR_COUNT(1, result);
|
||||
CHECK(toString(result.errors[0]) == R"(Redefinition of type 't0', previously defined at line 2)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_strip_indexer")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
|
||||
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type function stripindexer(tbl)
|
||||
if not tbl:is("table") then
|
||||
error("can only strip the indexer on a table!")
|
||||
end
|
||||
tbl:setindexer(types.never, types.never)
|
||||
return tbl
|
||||
end
|
||||
|
||||
type map = { [number]: string, foo: string }
|
||||
-- forcing an error here to check the exact type
|
||||
local function ok(tbl: stripindexer<map>): never return tbl end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
TypePackMismatch* tpm = get<TypePackMismatch>(result.errors[0]);
|
||||
REQUIRE(tpm);
|
||||
CHECK(toString(tpm->givenTp) == "{ foo: string }");
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -9,8 +9,8 @@
|
||||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauUserDefinedTypeFunctionsSyntax)
|
||||
LUAU_FASTFLAG(LuauUserDefinedTypeFunctions)
|
||||
LUAU_FASTFLAG(LuauUserDefinedTypeFunctionsSyntax2)
|
||||
LUAU_FASTFLAG(LuauUserDefinedTypeFunctions2)
|
||||
|
||||
TEST_SUITE_BEGIN("TypeAliases");
|
||||
|
||||
@ -1156,7 +1156,7 @@ type Foo<T> = Foo<T> | string
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "type_alias_adds_reduce_constraint_for_type_function")
|
||||
{
|
||||
if (!FFlag::LuauSolverV2 || !FFlag::LuauUserDefinedTypeFunctions)
|
||||
if (!FFlag::LuauSolverV2 || !FFlag::LuauUserDefinedTypeFunctions2)
|
||||
return;
|
||||
|
||||
CheckResult result = check(R"(
|
||||
@ -1170,8 +1170,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "type_alias_adds_reduce_constraint_for_type_f
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "user_defined_type_function_errors")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauUserDefinedTypeFunctionsSyntax, true};
|
||||
ScopedFastFlag noUDTFimpl{FFlag::LuauUserDefinedTypeFunctions, false};
|
||||
ScopedFastFlag sff{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
|
||||
ScopedFastFlag noUDTFimpl{FFlag::LuauUserDefinedTypeFunctions2, false};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type function foo()
|
||||
@ -1182,4 +1182,18 @@ TEST_CASE_FIXTURE(Fixture, "user_defined_type_function_errors")
|
||||
CHECK(toString(result.errors[0]) == "This syntax is not supported");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "bound_type_in_alias_segfault")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauSolverV2, true};
|
||||
|
||||
LUAU_CHECK_NO_ERRORS(check(R"(
|
||||
--!nonstrict
|
||||
type Map<T, V> = {[ K]: V}
|
||||
function foo:bar(): Config<any, any> end
|
||||
type Config<TSource, TContext> = Map<TSource, TContext> & { fields: FieldConfigMap<any, any>}
|
||||
export type FieldConfig<TSource, TContext, TArgs> = {[ string]: any}
|
||||
export type FieldConfigMap<TSource, TContext> = Map<string, FieldConfig<TSource, TContext>>
|
||||
)"));
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -20,8 +20,6 @@ LUAU_FASTFLAG(LuauInstantiateInSubtyping);
|
||||
LUAU_FASTFLAG(LuauSolverV2);
|
||||
LUAU_FASTINT(LuauTarjanChildLimit);
|
||||
|
||||
LUAU_DYNAMIC_FASTFLAG(LuauImproveNonFunctionCallError)
|
||||
|
||||
TEST_SUITE_BEGIN("TypeInferFunctions");
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "general_case_table_literal_blocks")
|
||||
@ -2340,20 +2338,10 @@ TEST_CASE_FIXTURE(Fixture, "attempt_to_call_an_intersection_of_tables")
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
if (DFFlag::LuauImproveNonFunctionCallError)
|
||||
{
|
||||
if (FFlag::LuauSolverV2)
|
||||
CHECK_EQ(toString(result.errors[0]), "Cannot call a value of type { x: number } & { y: string }");
|
||||
else
|
||||
CHECK_EQ(toString(result.errors[0]), "Cannot call a value of type {| x: number |}");
|
||||
}
|
||||
if (FFlag::LuauSolverV2)
|
||||
CHECK_EQ(toString(result.errors[0]), "Cannot call a value of type { x: number } & { y: string }");
|
||||
else
|
||||
{
|
||||
if (FFlag::LuauSolverV2)
|
||||
CHECK_EQ(toString(result.errors[0]), "Cannot call non-function { x: number } & { y: string }");
|
||||
else
|
||||
CHECK_EQ(toString(result.errors[0]), "Cannot call non-function {| x: number |}");
|
||||
}
|
||||
CHECK_EQ(toString(result.errors[0]), "Cannot call a value of type {| x: number |}");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "attempt_to_call_an_intersection_of_tables_with_call_metamethod")
|
||||
@ -2845,17 +2833,12 @@ TEST_CASE_FIXTURE(Fixture, "cannot_call_union_of_functions")
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
if (DFFlag::LuauImproveNonFunctionCallError)
|
||||
{
|
||||
std::string expected = R"(Cannot call a value of the union type:
|
||||
std::string expected = R"(Cannot call a value of the union type:
|
||||
| () -> ()
|
||||
| () -> () -> ()
|
||||
We are unable to determine the appropriate result type for such a call.)";
|
||||
|
||||
CHECK(expected == toString(result.errors[0]));
|
||||
}
|
||||
else
|
||||
CHECK("Cannot call non-function (() -> () -> ()) | (() -> ())" == toString(result.errors[0]));
|
||||
CHECK(expected == toString(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "fuzzer_missing_follow_in_ast_stat_fun")
|
||||
|
@ -16,8 +16,6 @@ using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
|
||||
LUAU_DYNAMIC_FASTFLAG(LuauImproveNonFunctionCallError)
|
||||
|
||||
TEST_SUITE_BEGIN("TypeInferLoops");
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "for_loop")
|
||||
@ -194,10 +192,7 @@ TEST_CASE_FIXTURE(Fixture, "for_in_loop_should_fail_with_non_function_iterator")
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
if (DFFlag::LuauImproveNonFunctionCallError)
|
||||
CHECK_EQ("Cannot call a value of type string", toString(result.errors[0]));
|
||||
else
|
||||
CHECK_EQ("Cannot call non-function string", toString(result.errors[0]));
|
||||
CHECK_EQ("Cannot call a value of type string", toString(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_with_just_one_iterator_is_ok")
|
||||
|
@ -608,4 +608,92 @@ local ReactShallowRenderer = require(game.A);
|
||||
)"));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "untitled_segfault_number_13")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauSolverV2, true};
|
||||
|
||||
fileResolver.source["game/A"] = R"(
|
||||
-- minimized from roblox-requests/http/src/response.lua
|
||||
local Response = {}
|
||||
Response.__index = Response
|
||||
function Response.new(content_type)
|
||||
-- creates response object from original request and roblox http response
|
||||
local self = setmetatable({}, Response)
|
||||
self.content_type = content_type
|
||||
return self
|
||||
end
|
||||
|
||||
function Response:xml(ignore_content_type)
|
||||
if ignore_content_type or self.content_type:find("+xml") or self.content_type:find("/xml") then
|
||||
else
|
||||
end
|
||||
end
|
||||
|
||||
---------------
|
||||
|
||||
return Response
|
||||
)";
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(check(R"(
|
||||
local _ = require(game.A);
|
||||
)"));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "spooky_blocked_type_laundered_by_bound_type")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauSolverV2, true};
|
||||
|
||||
fileResolver.source["game/A"] = R"(
|
||||
local Cache = {}
|
||||
|
||||
Cache.settings = {}
|
||||
|
||||
Cache.data = {}
|
||||
|
||||
function Cache.should_cache(url)
|
||||
url = url:split("?")[1]
|
||||
|
||||
for key, _ in pairs(Cache.settings) do
|
||||
if url:match('') then
|
||||
return key
|
||||
end
|
||||
end
|
||||
|
||||
return ""
|
||||
end
|
||||
|
||||
function Cache.is_cached(url, req_id)
|
||||
-- check local server cache first
|
||||
|
||||
local setting_key = Cache.should_cache(url)
|
||||
local settings = Cache.settings[setting_key]
|
||||
|
||||
if not setting_key then
|
||||
return false
|
||||
end
|
||||
|
||||
if Cache.data[req_id] ~= nil then
|
||||
return true
|
||||
end
|
||||
|
||||
if Cache.settings[setting_key].cache_globally then
|
||||
return false
|
||||
else
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
function Cache.get_expire(url)
|
||||
local setting_key = Cache.should_cache(url)
|
||||
return Cache.settings[setting_key].expires or math.huge
|
||||
end
|
||||
|
||||
return Cache
|
||||
)";
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(check(R"(
|
||||
local _ = require(game.A);
|
||||
)"));
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -17,7 +17,6 @@
|
||||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauRemoveBadRelationalOperatorWarning)
|
||||
|
||||
TEST_SUITE_BEGIN("TypeInferOperators");
|
||||
|
||||
@ -860,7 +859,7 @@ TEST_CASE_FIXTURE(Fixture, "error_on_invalid_operand_types_to_relational_operato
|
||||
)");
|
||||
|
||||
// If DCR is off and the flag to remove this check in the old solver is on, the expected behavior is no errors.
|
||||
if (!FFlag::LuauSolverV2 && FFlag::LuauRemoveBadRelationalOperatorWarning)
|
||||
if (!FFlag::LuauSolverV2)
|
||||
{
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
return;
|
||||
@ -1578,10 +1577,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "compare_singleton_string_to_string")
|
||||
|
||||
// There is a flag to gate turning this off, and this warning is not
|
||||
// implemented in the new solver, so assert there are no errors.
|
||||
if (FFlag::LuauRemoveBadRelationalOperatorWarning || FFlag::LuauSolverV2)
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
else
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "no_infinite_expansion_of_free_type" * doctest::timeout(1.0))
|
||||
|
@ -2371,4 +2371,57 @@ end
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(RefinementClassFixture, "typeof_instance_refinement")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: Instance | Vector3)
|
||||
if typeof(x) == "Instance" then
|
||||
local foo = x
|
||||
else
|
||||
local foo = x
|
||||
end
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ("Instance", toString(requireTypeAtPosition({3, 28})));
|
||||
CHECK_EQ("Vector3", toString(requireTypeAtPosition({5, 28})));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(RefinementClassFixture, "typeof_instance_error")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: Part)
|
||||
if typeof(x) == "Instance" then
|
||||
local foo : Folder = x
|
||||
end
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(RefinementClassFixture, "typeof_instance_isa_refinement")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: Part | Folder | string)
|
||||
if typeof(x) == "Instance" then
|
||||
local foo = x
|
||||
if foo:IsA("Folder") then
|
||||
local bar = foo
|
||||
end
|
||||
else
|
||||
local foo = x
|
||||
end
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ("Folder | Part", toString(requireTypeAtPosition({3, 28})));
|
||||
CHECK_EQ("Folder", toString(requireTypeAtPosition({5, 32})));
|
||||
CHECK_EQ("string", toString(requireTypeAtPosition({8, 28})));
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -20,7 +20,6 @@ LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||
LUAU_FASTFLAG(LuauFixIndexerSubtypingOrdering)
|
||||
LUAU_FASTFLAG(LuauAcceptIndexingTableUnionsIntersections)
|
||||
|
||||
LUAU_DYNAMIC_FASTFLAG(LuauImproveNonFunctionCallError)
|
||||
LUAU_DYNAMIC_FASTINT(LuauTypeSolverRelease)
|
||||
|
||||
TEST_SUITE_BEGIN("TableTests");
|
||||
@ -2407,7 +2406,7 @@ could not be converted into
|
||||
//
|
||||
// Second, nil <: unknown, so we consider that parameter to be optional.
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK("Type 'b1' could not be converted into 'a1'; at [read \"y\"], string is not exactly number" == toString(result.errors[0]));
|
||||
CHECK("Type 'b1' could not be converted into 'a1'; at table()[read \"y\"], string is not exactly number" == toString(result.errors[0]));
|
||||
}
|
||||
else if (FFlag::LuauInstantiateInSubtyping)
|
||||
{
|
||||
@ -2583,10 +2582,7 @@ b()
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
if (DFFlag::LuauImproveNonFunctionCallError)
|
||||
CHECK_EQ(toString(result.errors[0]), R"(Cannot call a value of type t1 where t1 = { @metatable { __call: t1 }, { } })");
|
||||
else
|
||||
CHECK_EQ(toString(result.errors[0]), R"(Cannot call non-function t1 where t1 = { @metatable { __call: t1 }, { } })");
|
||||
CHECK_EQ(toString(result.errors[0]), R"(Cannot call a value of type t1 where t1 = { @metatable { __call: t1 }, { } })");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "table_subtyping_shouldn't_add_optional_properties_to_sealed_tables")
|
||||
@ -3265,7 +3261,11 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_call_metamethod_must_be_callable")
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
if (!FFlag::LuauSolverV2)
|
||||
if (FFlag::LuauSolverV2)
|
||||
{
|
||||
CHECK("Cannot call a value of type a" == toString(result.errors[0]));
|
||||
}
|
||||
else
|
||||
{
|
||||
TypeError e{
|
||||
Location{{5, 20}, {5, 21}},
|
||||
@ -3273,14 +3273,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_call_metamethod_must_be_callable")
|
||||
};
|
||||
CHECK(result.errors[0] == e);
|
||||
}
|
||||
else if (DFFlag::LuauImproveNonFunctionCallError)
|
||||
{
|
||||
CHECK("Cannot call a value of type a" == toString(result.errors[0]));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK("Cannot call non-function a" == toString(result.errors[0]));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "table_call_metamethod_generic")
|
||||
@ -4851,4 +4843,27 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "length_of_array_is_number")
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "subtyping_with_a_metatable_table_path")
|
||||
{
|
||||
// Builtin functions have to be setup for the new solver
|
||||
if (!FFlag::LuauSolverV2)
|
||||
return;
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type self = {} & {}
|
||||
type Class = typeof(setmetatable())
|
||||
local function _(): Class
|
||||
return setmetatable({}::self, {})
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(
|
||||
"Type pack '{ @metatable { }, { } & { } }' could not be converted into 'Class'; at [0].metatable(), { } is not a subtype of nil\n"
|
||||
"\ttype { @metatable { }, { } & { } }[0].table()[0] ({ }) is not a subtype of Class[0].table() (nil)\n"
|
||||
"\ttype { @metatable { }, { } & { } }[0].table()[1] ({ }) is not a subtype of Class[0].table() (nil)",
|
||||
toString(result.errors[0])
|
||||
);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -235,6 +235,23 @@ TEST_CASE_FIXTURE(ClassFixture, "metatables")
|
||||
}
|
||||
|
||||
SUBCASE("table")
|
||||
{
|
||||
TYPESOLVE_CODE(R"(
|
||||
type Table = { foo: number }
|
||||
type Metatable = { bar: number }
|
||||
local tbl: Table = { foo = 123 }
|
||||
local mt: Metatable = { bar = 456 }
|
||||
local res = setmetatable(tbl, mt)
|
||||
)");
|
||||
|
||||
// Tricky test setup because 'setmetatable' mutates the argument 'tbl' type
|
||||
auto result = traverseForType(requireType("res"), Path(TypeField::Table), builtinTypes);
|
||||
auto expected = lookupType("Table");
|
||||
REQUIRE(expected);
|
||||
CHECK(result == follow(*expected));
|
||||
}
|
||||
|
||||
SUBCASE("metatable")
|
||||
{
|
||||
TYPESOLVE_CODE(R"(
|
||||
local mt = { foo = 123 }
|
||||
|
@ -1,5 +1,4 @@
|
||||
{
|
||||
"paths": ["GlobalLuauLibraries"],
|
||||
"aliases": {
|
||||
"dep": "this_should_be_overwritten_by_child_luaurc",
|
||||
"otherdep": "src/other_dependency"
|
||||
|
@ -1,5 +1,4 @@
|
||||
{
|
||||
"paths": ["../ProjectLuauLibraries"],
|
||||
"aliases": {
|
||||
"dep": "dependency",
|
||||
"subdir": "subdirectory"
|
||||
|
@ -1,2 +0,0 @@
|
||||
-- shouldn't attempt to search paths array because of "./" prefix
|
||||
return require("./library")
|
@ -1,2 +0,0 @@
|
||||
-- should be required using the paths array in the parent directory's .luaurc
|
||||
return require("global_library")
|
@ -1,2 +0,0 @@
|
||||
-- should be required using the paths array in .luaurc
|
||||
return require("library")
|
@ -0,0 +1 @@
|
||||
return {"result from dependency"}
|
@ -0,0 +1 @@
|
||||
return {"result from dependency"}
|
@ -0,0 +1 @@
|
||||
return {"result from dependency"}
|
@ -0,0 +1 @@
|
||||
return {"result from dependency"}
|
@ -0,0 +1,3 @@
|
||||
local result = require("./ambiguous/directory/dependency")
|
||||
result[#result+1] = "required into module"
|
||||
return result
|
@ -0,0 +1,3 @@
|
||||
local result = require("./ambiguous/file/dependency")
|
||||
result[#result+1] = "required into module"
|
||||
return result
|
@ -1,3 +1,3 @@
|
||||
local result = require("dependency")
|
||||
local result = require("./dependency")
|
||||
result[#result+1] = "required into module"
|
||||
return result
|
||||
|
Loading…
Reference in New Issue
Block a user