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:
Andy Friesen 2024-10-04 11:29:55 -07:00 committed by GitHub
parent 02241b6d24
commit 543de6e939
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
70 changed files with 1392 additions and 540 deletions

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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);

View File

@ -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,

View File

@ -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);

View File

@ -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,

View File

@ -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);
}
}
}

View File

@ -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)
{

View File

@ -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});
}

View File

@ -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))
{

View File

@ -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
{

View File

@ -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;

View File

@ -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(

View File

@ -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)

View File

@ -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);

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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,

View File

@ -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)

View File

@ -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;
}

View File

@ -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.

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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);
}

View File

@ -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

View File

@ -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);

View File

@ -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");

View File

@ -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");
}
}
}

View File

@ -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();

View File

@ -3634,6 +3634,10 @@ struct Compiler
{
// do nothing
}
else if (node->is<AstStatTypeFunction>())
{
// do nothing
}
else
{
LUAU_ASSERT(!"Unknown statement type");

View File

@ -32,7 +32,6 @@ struct Config
std::vector<std::string> globals;
std::vector<std::string> paths;
std::unordered_map<std::string, std::string> aliases;
};

View File

@ -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")

View File

@ -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");

View File

@ -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;

View File

@ -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),

View File

@ -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;

View File

@ -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);

View File

@ -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>

View File

@ -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);

View File

@ -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");

View File

@ -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")

View File

@ -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};

View File

@ -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>");
}

View File

@ -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 )";

View File

@ -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();

View File

@ -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();

View File

@ -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();

View File

@ -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")

View File

@ -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")

View File

@ -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();

View File

@ -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))

View File

@ -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();

View File

@ -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();

View File

@ -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 }

View File

@ -1,5 +1,4 @@
{
"paths": ["GlobalLuauLibraries"],
"aliases": {
"dep": "this_should_be_overwritten_by_child_luaurc",
"otherdep": "src/other_dependency"

View File

@ -1,5 +1,4 @@
{
"paths": ["../ProjectLuauLibraries"],
"aliases": {
"dep": "dependency",
"subdir": "subdirectory"

View File

@ -1,2 +0,0 @@
-- shouldn't attempt to search paths array because of "./" prefix
return require("./library")

View File

@ -1,2 +0,0 @@
-- should be required using the paths array in the parent directory's .luaurc
return require("global_library")

View File

@ -1,2 +0,0 @@
-- should be required using the paths array in .luaurc
return require("library")

View File

@ -0,0 +1 @@
return {"result from dependency"}

View File

@ -0,0 +1 @@
return {"result from dependency"}

View File

@ -0,0 +1 @@
return {"result from dependency"}

View File

@ -0,0 +1 @@
return {"result from dependency"}

View File

@ -0,0 +1,3 @@
local result = require("./ambiguous/directory/dependency")
result[#result+1] = "required into module"
return result

View File

@ -0,0 +1,3 @@
local result = require("./ambiguous/file/dependency")
result[#result+1] = "required into module"
return result

View File

@ -1,3 +1,3 @@
local result = require("dependency")
local result = require("./dependency")
result[#result+1] = "required into module"
return result