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>; using ScopePtr = std::shared_ptr<Scope>;
struct DcrLogger; struct DcrLogger;
struct TypeFunctionRuntime;
struct Inference struct Inference
{ {
@ -108,6 +109,8 @@ struct ConstraintGenerator
// Needed to be able to enable error-suppression preservation for immediate refinements. // Needed to be able to enable error-suppression preservation for immediate refinements.
NotNull<Normalizer> normalizer; 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. // Needed to resolve modules to make 'require' import types properly.
NotNull<ModuleResolver> moduleResolver; NotNull<ModuleResolver> moduleResolver;
// Occasionally constraint generation needs to produce an ICE. // Occasionally constraint generation needs to produce an ICE.
@ -125,6 +128,7 @@ struct ConstraintGenerator
ConstraintGenerator( ConstraintGenerator(
ModulePtr module, ModulePtr module,
NotNull<Normalizer> normalizer, NotNull<Normalizer> normalizer,
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
NotNull<ModuleResolver> moduleResolver, NotNull<ModuleResolver> moduleResolver,
NotNull<BuiltinTypes> builtinTypes, NotNull<BuiltinTypes> builtinTypes,
NotNull<InternalErrorReporter> ice, NotNull<InternalErrorReporter> ice,
@ -223,7 +227,10 @@ private:
); );
void applyRefinements(const ScopePtr& scope, Location location, RefinementId refinement); 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(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, AstStat* stat);
ControlFlow visit(const ScopePtr& scope, AstStatBlock* block); 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::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 struct SourceNode
{ {
bool hasDirtySourceModule() const bool hasDirtySourceModule() const

View File

@ -20,8 +20,6 @@ struct ModuleResolver
virtual ~ModuleResolver() {} virtual ~ModuleResolver() {}
/** Compute a ModuleName from an AST fragment. This AST fragment is generally the argument to the require() function. /** 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 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 * @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 BuiltinTypes;
struct TypeFunctionRuntime;
struct UnifierSharedState; struct UnifierSharedState;
struct TypeCheckLimits; struct TypeCheckLimits;
void checkNonStrict( void checkNonStrict(
NotNull<BuiltinTypes> builtinTypes, NotNull<BuiltinTypes> builtinTypes,
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
NotNull<InternalErrorReporter> ice, NotNull<InternalErrorReporter> ice,
NotNull<UnifierSharedState> unifierState, NotNull<UnifierSharedState> unifierState,
NotNull<const DataFlowGraph> dfg, NotNull<const DataFlowGraph> dfg,

View File

@ -613,20 +613,17 @@ struct TypeFunctionInstanceType
std::vector<TypePackId> packArguments; std::vector<TypePackId> packArguments;
std::optional<AstName> userFuncName; // Name of the user-defined type function; only available for UDTFs 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( TypeFunctionInstanceType(
NotNull<const TypeFunction> function, NotNull<const TypeFunction> function,
std::vector<TypeId> typeArguments, std::vector<TypeId> typeArguments,
std::vector<TypePackId> packArguments, std::vector<TypePackId> packArguments,
std::optional<AstName> userFuncName = std::nullopt, std::optional<AstName> userFuncName = std::nullopt
std::optional<AstExprFunction*> userFuncBody = std::nullopt
) )
: function(function) : function(function)
, typeArguments(typeArguments) , typeArguments(typeArguments)
, packArguments(packArguments) , packArguments(packArguments)
, userFuncName(userFuncName) , 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)>; using TypeIdPredicate = std::function<std::optional<TypeId>(TypeId)>;
std::vector<TypeId> filterMap(TypeId type, TypeIdPredicate predicate); 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(TypeId ty, const std::string& tagName);
void attachTag(Property& prop, const std::string& tagName); void attachTag(Property& prop, const std::string& tagName);

View File

@ -60,6 +60,7 @@ struct Reasonings
void check( void check(
NotNull<BuiltinTypes> builtinTypes, NotNull<BuiltinTypes> builtinTypes,
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
NotNull<UnifierSharedState> sharedState, NotNull<UnifierSharedState> sharedState,
NotNull<TypeCheckLimits> limits, NotNull<TypeCheckLimits> limits,
DcrLogger* logger, DcrLogger* logger,
@ -70,6 +71,7 @@ void check(
struct TypeChecker2 struct TypeChecker2
{ {
NotNull<BuiltinTypes> builtinTypes; NotNull<BuiltinTypes> builtinTypes;
NotNull<TypeFunctionRuntime> typeFunctionRuntime;
DcrLogger* logger; DcrLogger* logger;
const NotNull<TypeCheckLimits> limits; const NotNull<TypeCheckLimits> limits;
const NotNull<InternalErrorReporter> ice; const NotNull<InternalErrorReporter> ice;
@ -83,12 +85,12 @@ struct TypeChecker2
DenseHashSet<TypeId> seenTypeFunctionInstances{nullptr}; DenseHashSet<TypeId> seenTypeFunctionInstances{nullptr};
Normalizer normalizer; Normalizer normalizer;
TypeFunctionRuntime typeFunctionRuntime;
Subtyping _subtyping; Subtyping _subtyping;
NotNull<Subtyping> subtyping; NotNull<Subtyping> subtyping;
TypeChecker2( TypeChecker2(
NotNull<BuiltinTypes> builtinTypes, NotNull<BuiltinTypes> builtinTypes,
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
NotNull<UnifierSharedState> unifierState, NotNull<UnifierSharedState> unifierState,
NotNull<TypeCheckLimits> limits, NotNull<TypeCheckLimits> limits,
DcrLogger* logger, DcrLogger* logger,

View File

@ -12,6 +12,8 @@
#include <string> #include <string>
#include <optional> #include <optional>
struct lua_State;
namespace Luau namespace Luau
{ {
@ -20,11 +22,30 @@ struct TxnLog;
struct ConstraintSolver; struct ConstraintSolver;
class Normalizer; class Normalizer;
using StateRef = std::unique_ptr<lua_State, void (*)(lua_State*)>;
struct TypeFunctionRuntime 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 // For user-defined type functions, we store all generated types and packs for the duration of the typecheck
TypedAllocator<TypeFunctionType> typeArena; TypedAllocator<TypeFunctionType> typeArena;
TypedAllocator<TypeFunctionTypePackVar> typePackArena; 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 struct TypeFunctionContext
@ -43,7 +64,6 @@ struct TypeFunctionContext
const Constraint* constraint; const Constraint* constraint;
std::optional<AstName> userFuncName; // Name of the user-defined type function; only available for UDTFs 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); 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. /// Represents fields of a type or pack that contain a type.
enum class TypeField enum class TypeField
{ {
/// The table of a metatable type.
Table,
/// The metatable of a type. This could be a metatable type, a primitive /// The metatable of a type. This could be a metatable type, a primitive
/// type, a class type, or perhaps even a string singleton type. /// type, a class type, or perhaps even a string singleton type.
Metatable, Metatable,

View File

@ -13,6 +13,8 @@
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAGVARIABLE(LuauDocumentationAtPosition, false)
namespace Luau namespace Luau
{ {
@ -509,6 +511,38 @@ static std::optional<DocumentationSymbol> checkOverloadedDocumentationSymbol(
return documentationSymbol; 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::optional<DocumentationSymbol> getDocumentationSymbolAtPosition(const SourceModule& source, const Module& module, Position position)
{ {
std::vector<AstNode*> ancestry = findAstAncestryOfPosition(source, 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)) 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) if (auto propIt = ctv->props.find(indexName->index.value); propIt != ctv->props.end())
return checkOverloadedDocumentationSymbol(module, *ty, parentExpr, propIt->second.documentationSymbol); {
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) 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) if (FFlag::LuauAutocompleteNewSolverLimit)
{ {

View File

@ -29,6 +29,7 @@
LUAU_FASTINT(LuauCheckRecursionLimit); LUAU_FASTINT(LuauCheckRecursionLimit);
LUAU_FASTFLAG(DebugLuauLogSolverToJson); LUAU_FASTFLAG(DebugLuauLogSolverToJson);
LUAU_FASTFLAG(DebugLuauMagicTypes); LUAU_FASTFLAG(DebugLuauMagicTypes);
LUAU_DYNAMIC_FASTINT(LuauTypeSolverRelease);
namespace Luau namespace Luau
{ {
@ -191,6 +192,7 @@ bool hasFreeType(TypeId ty)
ConstraintGenerator::ConstraintGenerator( ConstraintGenerator::ConstraintGenerator(
ModulePtr module, ModulePtr module,
NotNull<Normalizer> normalizer, NotNull<Normalizer> normalizer,
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
NotNull<ModuleResolver> moduleResolver, NotNull<ModuleResolver> moduleResolver,
NotNull<BuiltinTypes> builtinTypes, NotNull<BuiltinTypes> builtinTypes,
NotNull<InternalErrorReporter> ice, NotNull<InternalErrorReporter> ice,
@ -206,6 +208,7 @@ ConstraintGenerator::ConstraintGenerator(
, rootScope(nullptr) , rootScope(nullptr)
, dfg(dfg) , dfg(dfg)
, normalizer(normalizer) , normalizer(normalizer)
, typeFunctionRuntime(typeFunctionRuntime)
, moduleResolver(moduleResolver) , moduleResolver(moduleResolver)
, ice(ice) , ice(ice)
, globalScope(globalScope) , globalScope(globalScope)
@ -237,7 +240,8 @@ void ConstraintGenerator::visitModuleRoot(AstStatBlock* block)
Checkpoint start = checkpoint(this); 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) if (cf == ControlFlow::None)
addConstraint(scope, block->location, PackSubtypeConstraint{builtinTypes->emptyTypePack, rootScope->returnType}); 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); 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) ControlFlow ConstraintGenerator::visitBlockWithoutChildScope(const ScopePtr& scope, AstStatBlock* block)
{ {
RecursionCounter counter{&recursionCount}; RecursionCounter counter{&recursionCount};
@ -653,6 +760,29 @@ ControlFlow ConstraintGenerator::visitBlockWithoutChildScope(const ScopePtr& sco
return ControlFlow::None; 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; std::unordered_map<Name, Location> aliasDefinitionLocations;
// In order to enable mutually-recursive type aliases, we need to // In order to enable mutually-recursive type aliases, we need to
@ -709,6 +839,12 @@ ControlFlow ConstraintGenerator::visitBlockWithoutChildScope(const ScopePtr& sco
continue; continue;
} }
if (scope->parent != globalScope)
{
reportError(function->location, GenericError{"Local user-defined functions are not supported yet"});
continue;
}
ScopePtr defnScope = childScope(function, scope); ScopePtr defnScope = childScope(function, scope);
// Create TypeFunctionInstanceType // Create TypeFunctionInstanceType
@ -729,12 +865,14 @@ ControlFlow ConstraintGenerator::visitBlockWithoutChildScope(const ScopePtr& sco
quantifiedTypeParams.push_back(genericTy); quantifiedTypeParams.push_back(genericTy);
} }
if (std::optional<std::string> error = typeFunctionRuntime->registerFunction(function))
reportError(function->location, GenericError{*error});
TypeId typeFunctionTy = arena->addType(TypeFunctionInstanceType{ TypeId typeFunctionTy = arena->addType(TypeFunctionInstanceType{
NotNull{&builtinTypeFunctions().userFunc}, NotNull{&builtinTypeFunctions().userFunc},
std::move(typeParams), std::move(typeParams),
{}, {},
function->name, function->name,
function->body,
}); });
TypeFun typeFunction{std::move(quantifiedTypeParams), typeFunctionTy}; TypeFun typeFunction{std::move(quantifiedTypeParams), typeFunctionTy};
@ -1091,7 +1229,10 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatRepeat* rep
{ {
ScopePtr repeatScope = childScope(repeat, scope); 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); check(repeatScope, repeat->condition);
@ -1265,7 +1406,8 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatBlock* bloc
{ {
ScopePtr innerScope = childScope(block, scope); 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 // 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. // 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; TypeFun typeFunction = bindingIt->second;
// Adding typeAliasExpansionConstraint on user-defined type function for the constraint solver // 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}); TypeId expansionTy = arena->addType(PendingExpansionType{{}, function->name, typeFunctionTy->typeArguments, typeFunctionTy->packArguments});
addConstraint(scope, function->location, TypeAliasExpansionConstraint{/* target */ expansionTy}); addConstraint(scope, function->location, TypeAliasExpansionConstraint{/* target */ expansionTy});
@ -2511,7 +2653,7 @@ std::tuple<TypeId, TypeId, RefinementId> ConstraintGenerator::checkBinary(
TypeId ty = follow(typeFun->type); TypeId ty = follow(typeFun->type);
// We're only interested in the root class of any classes. // 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; discriminantTy = ty;
} }
@ -2944,7 +3086,8 @@ ConstraintGenerator::FunctionSignature ConstraintGenerator::checkFunctionSignatu
void ConstraintGenerator::checkFunctionBody(const ScopePtr& scope, AstExprFunction* fn) 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 () // 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) if (cf == ControlFlow::None)
addConstraint(scope, fn->location, PackSubtypeConstraint{builtinTypes->emptyTypePack, scope->returnType}); 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) auto bindResult = [this, &c, constraint](TypeId result)
{ {
LUAU_ASSERT(get<PendingExpansionType>(c.target)); if (DFInt::LuauTypeSolverRelease >= 646)
shiftReferences(c.target, result); {
bind(constraint, c.target, result); 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) 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 // Due to how pending expansion types and TypeFun's are created
// If this check passes, we have created a cyclic / corecursive type alias // If this check passes, we have created a cyclic / corecursive type alias
// of size 0 // of size 0
TypeId lhs = c.target; TypeId lhs = DFInt::LuauTypeSolverRelease >= 646 ? follow(c.target) : c.target;
TypeId rhs = tf->type; TypeId rhs = tf->type;
if (occursCheck(lhs, rhs)) if (occursCheck(lhs, rhs))
{ {

View File

@ -18,8 +18,6 @@
LUAU_FASTINTVARIABLE(LuauIndentTypeMismatchMaxTypeLength, 10) LUAU_FASTINTVARIABLE(LuauIndentTypeMismatchMaxTypeLength, 10)
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauImproveNonFunctionCallError, false)
static std::string wrongNumberOfArgsString( static std::string wrongNumberOfArgsString(
size_t expectedCount, size_t expectedCount,
std::optional<size_t> maximumCount, std::optional<size_t> maximumCount,
@ -408,35 +406,30 @@ struct ErrorConverter
std::string operator()(const Luau::CannotCallNonFunction& e) const 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); err += "\n | " + toString(option);
continue;
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 += "\nWe are unable to determine the appropriate result type for such a call."; // 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 err;
} }
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 std::string operator()(const Luau::ExtraInformation& e) const
{ {

View File

@ -45,6 +45,7 @@ LUAU_FASTFLAGVARIABLE(DebugLuauForbidInternalTypes, false)
LUAU_FASTFLAGVARIABLE(DebugLuauForceStrictMode, false) LUAU_FASTFLAGVARIABLE(DebugLuauForceStrictMode, false)
LUAU_FASTFLAGVARIABLE(DebugLuauForceNonStrictMode, false) LUAU_FASTFLAGVARIABLE(DebugLuauForceNonStrictMode, false)
LUAU_FASTFLAGVARIABLE(LuauSourceModuleUpdatedWithSelectedMode, false) LUAU_FASTFLAGVARIABLE(LuauSourceModuleUpdatedWithSelectedMode, false)
LUAU_FASTFLAGVARIABLE(LuauUserDefinedTypeFunctionNoEvaluation, false)
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauRunCustomModuleChecks, false) LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauRunCustomModuleChecks, false)
LUAU_FASTFLAG(StudioReportLuauAny2) LUAU_FASTFLAG(StudioReportLuauAny2)
@ -205,72 +206,6 @@ LoadDefinitionFileResult Frontend::loadDefinitionFile(
return LoadDefinitionFileResult{true, parseResult, sourceModule, checkedModule}; 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 namespace
{ {
@ -1383,11 +1318,15 @@ ModulePtr check(
unifierState.counters.iterationLimit = limits.unifierIterationLimit.value_or(FInt::LuauTypeInferIterationLimit); unifierState.counters.iterationLimit = limits.unifierIterationLimit.value_or(FInt::LuauTypeInferIterationLimit);
Normalizer normalizer{&result->internalTypes, builtinTypes, NotNull{&unifierState}}; Normalizer normalizer{&result->internalTypes, builtinTypes, NotNull{&unifierState}};
TypeFunctionRuntime typeFunctionRuntime; TypeFunctionRuntime typeFunctionRuntime{iceHandler, NotNull{&limits}};
if (FFlag::LuauUserDefinedTypeFunctionNoEvaluation)
typeFunctionRuntime.allowEvaluation = sourceModule.parseErrors.empty();
ConstraintGenerator cg{ ConstraintGenerator cg{
result, result,
NotNull{&normalizer}, NotNull{&normalizer},
NotNull{&typeFunctionRuntime},
moduleResolver, moduleResolver,
builtinTypes, builtinTypes,
iceHandler, iceHandler,
@ -1463,12 +1402,23 @@ ModulePtr check(
switch (mode) switch (mode)
{ {
case Mode::Nonstrict: 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; break;
case Mode::Definition: case Mode::Definition:
// fallthrough intentional // fallthrough intentional
case Mode::Strict: 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; break;
case Mode::NoCheck: case Mode::NoCheck:
break; break;

View File

@ -528,7 +528,12 @@ struct TypeCacher : TypeOnceVisitor
DenseHashSet<TypePackId> uncacheablePacks{nullptr}; DenseHashSet<TypePackId> uncacheablePacks{nullptr};
explicit TypeCacher(NotNull<DenseHashSet<TypeId>> cachedTypes) 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) , cachedTypes(cachedTypes)
{ {
} }
@ -565,9 +570,33 @@ struct TypeCacher : TypeOnceVisitor
bool visit(TypeId ty) override 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 false;
return true; }
else
{
return true;
}
} }
bool visit(TypeId ty, const FreeType& ft) override bool visit(TypeId ty, const FreeType& ft) override
@ -592,6 +621,19 @@ struct TypeCacher : TypeOnceVisitor
return false; 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 bool visit(TypeId ty, const PrimitiveType&) override
{ {
cache(ty); cache(ty);
@ -729,6 +771,24 @@ struct TypeCacher : TypeOnceVisitor
return false; 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 bool visit(TypeId ty, const ClassType&) override
{ {
cache(ty); cache(ty);
@ -843,12 +903,38 @@ struct TypeCacher : TypeOnceVisitor
return false; 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 bool visit(TypePackId tp, const FreeTypePack&) override
{ {
markUncacheable(tp); markUncacheable(tp);
return false; 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 bool visit(TypePackId tp, const VariadicTypePack& vtp) override
{ {
if (isUncacheable(tp)) if (isUncacheable(tp))
@ -884,6 +970,27 @@ struct TypeCacher : TypeOnceVisitor
return true; 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( std::optional<TypeId> generalize(

View File

@ -146,7 +146,20 @@ struct ClonePublicInterface : Substitution
{ {
if (auto freety = getMutable<FreeType>(result)) 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)) else if (auto genericty = getMutable<GenericType>(result))
{ {
@ -159,7 +172,35 @@ struct ClonePublicInterface : Substitution
TypePackId clean(TypePackId tp) override 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) TypeId cloneType(TypeId ty)

View File

@ -154,13 +154,12 @@ private:
struct NonStrictTypeChecker struct NonStrictTypeChecker
{ {
NotNull<BuiltinTypes> builtinTypes; NotNull<BuiltinTypes> builtinTypes;
NotNull<TypeFunctionRuntime> typeFunctionRuntime;
const NotNull<InternalErrorReporter> ice; const NotNull<InternalErrorReporter> ice;
NotNull<TypeArena> arena; NotNull<TypeArena> arena;
Module* module; Module* module;
Normalizer normalizer; Normalizer normalizer;
TypeFunctionRuntime typeFunctionRuntime;
Subtyping subtyping; Subtyping subtyping;
NotNull<const DataFlowGraph> dfg; NotNull<const DataFlowGraph> dfg;
DenseHashSet<TypeId> noTypeFunctionErrors{nullptr}; DenseHashSet<TypeId> noTypeFunctionErrors{nullptr};
@ -172,6 +171,7 @@ struct NonStrictTypeChecker
NonStrictTypeChecker( NonStrictTypeChecker(
NotNull<TypeArena> arena, NotNull<TypeArena> arena,
NotNull<BuiltinTypes> builtinTypes, NotNull<BuiltinTypes> builtinTypes,
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
const NotNull<InternalErrorReporter> ice, const NotNull<InternalErrorReporter> ice,
NotNull<UnifierSharedState> unifierState, NotNull<UnifierSharedState> unifierState,
NotNull<const DataFlowGraph> dfg, NotNull<const DataFlowGraph> dfg,
@ -179,11 +179,12 @@ struct NonStrictTypeChecker
Module* module Module* module
) )
: builtinTypes(builtinTypes) : builtinTypes(builtinTypes)
, typeFunctionRuntime(typeFunctionRuntime)
, ice(ice) , ice(ice)
, arena(arena) , arena(arena)
, module(module) , module(module)
, normalizer{arena, builtinTypes, unifierState, /* cache inhabitance */ true} , normalizer{arena, builtinTypes, unifierState, /* cache inhabitance */ true}
, subtyping{builtinTypes, arena, NotNull(&normalizer), NotNull(&typeFunctionRuntime), ice} , subtyping{builtinTypes, arena, NotNull(&normalizer), typeFunctionRuntime, ice}
, dfg(dfg) , dfg(dfg)
, limits(limits) , limits(limits)
{ {
@ -228,14 +229,13 @@ struct NonStrictTypeChecker
if (noTypeFunctionErrors.find(instance)) if (noTypeFunctionErrors.find(instance))
return instance; return instance;
ErrorVec errors = ErrorVec errors = reduceTypeFunctions(
reduceTypeFunctions( instance,
instance, location,
location, TypeFunctionContext{arena, builtinTypes, stack.back(), NotNull{&normalizer}, typeFunctionRuntime, ice, limits},
TypeFunctionContext{arena, builtinTypes, stack.back(), NotNull{&normalizer}, NotNull{&typeFunctionRuntime}, ice, limits}, true
true )
) .errors;
.errors;
if (errors.empty()) if (errors.empty())
noTypeFunctionErrors.insert(instance); noTypeFunctionErrors.insert(instance);
@ -760,6 +760,7 @@ private:
void checkNonStrict( void checkNonStrict(
NotNull<BuiltinTypes> builtinTypes, NotNull<BuiltinTypes> builtinTypes,
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
NotNull<InternalErrorReporter> ice, NotNull<InternalErrorReporter> ice,
NotNull<UnifierSharedState> unifierState, NotNull<UnifierSharedState> unifierState,
NotNull<const DataFlowGraph> dfg, NotNull<const DataFlowGraph> dfg,
@ -770,7 +771,7 @@ void checkNonStrict(
{ {
LUAU_TIMETRACE_SCOPE("checkNonStrict", "Typechecking"); 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); typeChecker.visit(sourceModule.root);
unfreeze(module->interfaceTypes); unfreeze(module->interfaceTypes);
copyErrors(module->errors, module->interfaceTypes, builtinTypes); copyErrors(module->errors, module->interfaceTypes, builtinTypes);

View File

@ -16,8 +16,6 @@
#include "Luau/Unifier.h" #include "Luau/Unifier.h"
LUAU_FASTFLAGVARIABLE(DebugLuauCheckNormalizeInvariant, false) LUAU_FASTFLAGVARIABLE(DebugLuauCheckNormalizeInvariant, false)
LUAU_FASTFLAGVARIABLE(LuauNormalizeAwayUninhabitableTables, false)
LUAU_FASTFLAGVARIABLE(LuauNormalizeNotUnknownIntersection, false);
LUAU_FASTFLAGVARIABLE(LuauFixReduceStackPressure, false); LUAU_FASTFLAGVARIABLE(LuauFixReduceStackPressure, false);
LUAU_FASTFLAGVARIABLE(LuauFixCyclicTablesBlowingStack, false); LUAU_FASTFLAGVARIABLE(LuauFixCyclicTablesBlowingStack, false);
@ -40,12 +38,6 @@ static bool fixCyclicTablesBlowingStack()
namespace Luau 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) static bool shouldEarlyExit(NormalizationResult res)
{ {
// if res is hit limits, return control flow // 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 // TODO: remove unions of tables where possible
// we can always skip `never` // we can always skip `never`
if (normalizeAwayUninhabitableTables() && get<NeverType>(there)) if (get<NeverType>(there))
return; return;
heres.insert(there); heres.insert(there);
@ -2619,13 +2611,12 @@ std::optional<TypeId> Normalizer::intersectionOfTables(TypeId here, TypeId there
seenSet.erase(*tprop.readTy); seenSet.erase(*tprop.readTy);
} }
if (normalizeAwayUninhabitableTables() && NormalizationResult::True != res) if (NormalizationResult::True != res)
return {builtinTypes->neverType}; return {builtinTypes->neverType};
} }
else else
{ {
if (normalizeAwayUninhabitableTables() && if (NormalizationResult::False == isIntersectionInhabited(*hprop.readTy, *tprop.readTy))
NormalizationResult::False == isIntersectionInhabited(*hprop.readTy, *tprop.readTy))
return {builtinTypes->neverType}; return {builtinTypes->neverType};
} }
@ -3258,7 +3249,7 @@ NormalizationResult Normalizer::intersectNormalWithTy(NormalizedType& here, Type
// this is a noop since an intersection with `unknown` is trivial. // this is a noop since an intersection with `unknown` is trivial.
return NormalizationResult::True; 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` // if we're intersecting with `~unknown`, this is equivalent to intersecting with `never`
// this means we should clear the type entirely. // 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}; UnifierSharedState sharedState{&ice};
TypeArena arena; TypeArena arena;
Normalizer normalizer{&arena, builtinTypes, NotNull{&sharedState}}; 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! // Subtyping under DCR is not implemented using unification!
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
@ -3457,7 +3451,10 @@ bool isSubtype(TypePackId subPack, TypePackId superPack, NotNull<Scope> scope, N
UnifierSharedState sharedState{&ice}; UnifierSharedState sharedState{&ice};
TypeArena arena; TypeArena arena;
Normalizer normalizer{&arena, builtinTypes, NotNull{&sharedState}}; 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! // Subtyping under DCR is not implemented using unification!
if (FFlag::LuauSolverV2) 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}); return dest.addType(NegationType{a.ty});
else if constexpr (std::is_same_v<T, TypeFunctionInstanceType>) 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)); return dest.addType(std::move(clone));
} }
else 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) SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const MetatableType* subMt, const MetatableType* superMt, NotNull<Scope> scope)
{ {
return isCovariantWith(env, subMt->table, superMt->table, scope) if (DFInt::LuauTypeSolverRelease >= 646)
.andAlso(isCovariantWith(env, subMt->metatable, superMt->metatable, scope).withBothComponent(TypePath::TypeField::Metatable)); {
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) SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const MetatableType* subMt, const TableType* superTable, NotNull<Scope> scope)

View File

@ -31,7 +31,7 @@
#include <ostream> #include <ostream>
LUAU_FASTFLAG(DebugLuauMagicTypes) LUAU_FASTFLAG(DebugLuauMagicTypes)
LUAU_FASTFLAG(LuauUserDefinedTypeFunctions) LUAU_FASTFLAG(LuauUserDefinedTypeFunctions2)
LUAU_DYNAMIC_FASTINT(LuauTypeSolverRelease) LUAU_DYNAMIC_FASTINT(LuauTypeSolverRelease)
namespace Luau namespace Luau
@ -268,6 +268,7 @@ struct InternalTypeFunctionFinder : TypeOnceVisitor
void check( void check(
NotNull<BuiltinTypes> builtinTypes, NotNull<BuiltinTypes> builtinTypes,
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
NotNull<UnifierSharedState> unifierState, NotNull<UnifierSharedState> unifierState,
NotNull<TypeCheckLimits> limits, NotNull<TypeCheckLimits> limits,
DcrLogger* logger, DcrLogger* logger,
@ -277,7 +278,7 @@ void check(
{ {
LUAU_TIMETRACE_SCOPE("check", "Typechecking"); 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); typeChecker.visit(sourceModule.root);
@ -294,6 +295,7 @@ void check(
TypeChecker2::TypeChecker2( TypeChecker2::TypeChecker2(
NotNull<BuiltinTypes> builtinTypes, NotNull<BuiltinTypes> builtinTypes,
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
NotNull<UnifierSharedState> unifierState, NotNull<UnifierSharedState> unifierState,
NotNull<TypeCheckLimits> limits, NotNull<TypeCheckLimits> limits,
DcrLogger* logger, DcrLogger* logger,
@ -301,13 +303,14 @@ TypeChecker2::TypeChecker2(
Module* module Module* module
) )
: builtinTypes(builtinTypes) : builtinTypes(builtinTypes)
, typeFunctionRuntime(typeFunctionRuntime)
, logger(logger) , logger(logger)
, limits(limits) , limits(limits)
, ice(unifierState->iceHandler) , ice(unifierState->iceHandler)
, sourceModule(sourceModule) , sourceModule(sourceModule)
, module(module) , module(module)
, normalizer{&module->internalTypes, builtinTypes, unifierState, /* cacheInhabitance */ true} , 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) , subtyping(&_subtyping)
{ {
} }
@ -489,9 +492,7 @@ TypeId TypeChecker2::checkForTypeFunctionInhabitance(TypeId instance, Location l
reduceTypeFunctions( reduceTypeFunctions(
instance, instance,
location, location,
TypeFunctionContext{ TypeFunctionContext{NotNull{&module->internalTypes}, builtinTypes, stack.back(), NotNull{&normalizer}, typeFunctionRuntime, ice, limits},
NotNull{&module->internalTypes}, builtinTypes, stack.back(), NotNull{&normalizer}, NotNull{&typeFunctionRuntime}, ice, limits
},
true true
) )
.errors; .errors;
@ -1198,7 +1199,7 @@ void TypeChecker2::visit(AstStatTypeAlias* stat)
void TypeChecker2::visit(AstStatTypeFunction* stat) void TypeChecker2::visit(AstStatTypeFunction* stat)
{ {
// TODO: add type checking for user-defined type functions // 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"}}); reportError(TypeError{stat->location, GenericError{"This syntax is not supported"}});
} }
@ -1450,7 +1451,7 @@ void TypeChecker2::visitCall(AstExprCall* call)
builtinTypes, builtinTypes,
NotNull{&module->internalTypes}, NotNull{&module->internalTypes},
NotNull{&normalizer}, NotNull{&normalizer},
NotNull{&typeFunctionRuntime}, typeFunctionRuntime,
NotNull{stack.back()}, NotNull{stack.back()},
ice, ice,
limits, limits,

View File

@ -46,7 +46,8 @@ LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFamilyApplicationCartesianProductLimit, 5'0
LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFamilyUseGuesserDepth, -1); LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFamilyUseGuesserDepth, -1);
LUAU_FASTFLAGVARIABLE(DebugLuauLogTypeFamilies, false) LUAU_FASTFLAGVARIABLE(DebugLuauLogTypeFamilies, false)
LUAU_FASTFLAGVARIABLE(LuauUserDefinedTypeFunctions, false) LUAU_FASTFLAGVARIABLE(LuauUserDefinedTypeFunctions2, false)
LUAU_FASTFLAG(LuauUserDefinedTypeFunctionNoEvaluation)
LUAU_DYNAMIC_FASTINT(LuauTypeSolverRelease) LUAU_DYNAMIC_FASTINT(LuauTypeSolverRelease)
@ -375,7 +376,6 @@ struct TypeFunctionReducer
return; return;
ctx.userFuncName = tfit->userFuncName; ctx.userFuncName = tfit->userFuncName;
ctx.userFuncBody = tfit->userFuncBody;
TypeFunctionReductionResult<TypeId> result = tfit->function->reducer(subject, tfit->typeArguments, tfit->packArguments, NotNull{&ctx}); TypeFunctionReductionResult<TypeId> result = tfit->function->reducer(subject, tfit->typeArguments, tfit->packArguments, NotNull{&ctx});
handleTypeFunctionReduction(subject, result); 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( static FunctionGraphReductionResult reduceFunctionsInternal(
VecDeque<TypeId> queuedTys, VecDeque<TypeId> queuedTys,
VecDeque<TypePackId> queuedTps, VecDeque<TypePackId> queuedTps,
@ -586,8 +600,6 @@ static std::optional<TypeFunctionReductionResult<TypeId>> tryDistributeTypeFunct
return std::nullopt; return std::nullopt;
} }
using StateRef = std::unique_ptr<lua_State, void (*)(lua_State*)>;
TypeFunctionReductionResult<TypeId> userDefinedTypeFunction( TypeFunctionReductionResult<TypeId> userDefinedTypeFunction(
TypeId instance, TypeId instance,
const std::vector<TypeId>& typeParams, const std::vector<TypeId>& typeParams,
@ -595,12 +607,19 @@ TypeFunctionReductionResult<TypeId> userDefinedTypeFunction(
NotNull<TypeFunctionContext> ctx 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"); ctx->ice->ice("all user-defined type functions must have an associated function definition");
return {std::nullopt, true, {}, {}}; 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) for (auto typeParam : typeParams)
{ {
TypeId ty = follow(typeParam); TypeId ty = follow(typeParam);
@ -611,62 +630,18 @@ TypeFunctionReductionResult<TypeId> userDefinedTypeFunction(
} }
AstName name = *ctx->userFuncName; AstName name = *ctx->userFuncName;
AstExprFunction* function = *ctx->userFuncBody;
// Construct ParseResult containing the type function lua_State* global = ctx->typeFunctionRuntime->state.get();
Allocator allocator;
AstNameTable names(allocator);
AstExprGlobal globalName{Location{}, name}; if (global == nullptr)
AstStatFunction typeFunction{Location{}, &globalName, function}; return {std::nullopt, true, {}, {}, format("'%s' type function: cannot be evaluated in this context", name.value)};
AstStat* stmtArray[] = {&typeFunction};
AstArray<AstStat*> stmts{stmtArray, 1};
AstStatBlock exec{Location{}, stmts};
ParseResult parseResult{&exec, 1};
BytecodeBuilder builder; // Separate sandboxed thread for individual execution and private globals
try lua_State* L = lua_newthread(global);
{ LuauTempThreadPopper popper(global);
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};
}
std::string bytecode = builder.getBytecode(); lua_getglobal(global, name.value);
lua_xmove(global, L, 1);
// 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};
}
// Push serialized arguments onto the stack // 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. // 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) 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) 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()) 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}; return {std::nullopt, true, {}, {}, error};
// If the return value is not a type userdata, return with error message // 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, {}, {}}; return {ctx->builtins->numberType, false, {}, {}};
// we use the normalized operand here in case there was an intersection or union. // 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)) if (normTy->hasTopTable() || get<TableType>(normalizedOperand))
return {ctx->builtins->numberType, false, {}, {}}; return {ctx->builtins->numberType, false, {}, {}};
@ -947,6 +923,108 @@ TypeFunctionReductionResult<TypeId> unmTypeFunction(
return {std::nullopt, true, {}, {}}; 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) TypeFunctionContext::TypeFunctionContext(NotNull<ConstraintSolver> cs, NotNull<Scope> scope, NotNull<const Constraint> constraint)
: arena(cs->arena) : arena(cs->arena)
, builtins(cs->builtinTypes) , builtins(cs->builtinTypes)

View File

@ -13,8 +13,8 @@
#include <set> #include <set>
#include <vector> #include <vector>
// defined in TypeFunctionRuntimeBuilder.cpp LUAU_DYNAMIC_FASTINT(LuauTypeFunctionSerdeIterationLimit)
LUAU_DYNAMIC_FASTINT(LuauTypeFunctionSerdeIterationLimit); LUAU_DYNAMIC_FASTINT(LuauTypeSolverRelease)
namespace Luau 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) TypeFunctionType* allocateTypeFunctionType(lua_State* L, TypeFunctionTypeVariant type)
{ {
auto ctx = getTypeFunctionContext(L); auto ctx = getTypeFunctionRuntime(L);
return ctx->typeFunctionRuntime->typeArena.allocate(std::move(type)); return ctx->typeArena.allocate(std::move(type));
} }
TypeFunctionTypePackVar* allocateTypeFunctionTypePack(lua_State* L, TypeFunctionTypePackVariant type) TypeFunctionTypePackVar* allocateTypeFunctionTypePack(lua_State* L, TypeFunctionTypePackVariant type)
{ {
auto ctx = getTypeFunctionContext(L); auto ctx = getTypeFunctionRuntime(L);
return ctx->typeFunctionRuntime->typePackArena.allocate(std::move(type)); return ctx->typePackArena.allocate(std::move(type));
} }
// Pushes a new type userdata onto the stack // 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)` // 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) static int setTableIndexer(lua_State* L)
{ {
int argumentCount = lua_gettop(L); int argumentCount = lua_gettop(L);
@ -693,8 +693,16 @@ static int setTableIndexer(lua_State* L)
TypeFunctionTypeId key = getTypeUserData(L, 2); TypeFunctionTypeId key = getTypeUserData(L, 2);
TypeFunctionTypeId value = getTypeUserData(L, 3); 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; return 0;
} }
@ -1353,7 +1361,7 @@ static int deepCopy(lua_State* L)
TypeFunctionTypeId arg = getTypeUserData(L, 1); TypeFunctionTypeId arg = getTypeUserData(L, 1);
TypeFunctionTypeId copy = deepClone(getTypeFunctionContext(L)->typeFunctionRuntime, arg); TypeFunctionTypeId copy = deepClone(NotNull{getTypeFunctionRuntime(L)}, arg);
allocTypeUserData(L, copy->type); allocTypeUserData(L, copy->type);
return 1; return 1;
} }

View File

@ -32,7 +32,6 @@ LUAU_FASTINTVARIABLE(LuauVisitRecursionLimit, 500)
LUAU_FASTFLAG(LuauKnowsTheDataModel3) LUAU_FASTFLAG(LuauKnowsTheDataModel3)
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false) LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false)
LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAGVARIABLE(LuauRemoveBadRelationalOperatorWarning, false)
LUAU_FASTFLAGVARIABLE(LuauAcceptIndexingTableUnionsIntersections, false) LUAU_FASTFLAGVARIABLE(LuauAcceptIndexingTableUnionsIntersections, false)
namespace Luau namespace Luau
@ -2794,35 +2793,20 @@ TypeId TypeChecker::checkRelationalOperation(
{ {
reportErrors(state.errors); 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. reportError(
// However, the old solver does not readily have the ability to discern if the union is comparable. expr.location,
// This is the case when the lhs is e.g. a union of singletons and the rhs is the combined type. GenericError{
// The new solver has much more powerful logic for resolving relational operators, but for now, format("Type '%s' cannot be compared with relational operator %s", toString(leftType).c_str(), toString(expr.op).c_str())
// 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())
}
); );
} }
}
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; 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. // 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)); 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. // 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) switch (field)
{ {
case TypePath::TypeField::Table:
if (auto mt = get<MetatableType>(current))
{
updateCurrent(mt->table);
return true;
}
return false;
case TypePath::TypeField::Metatable: case TypePath::TypeField::Metatable:
if (auto currentType = get<TypeId>(current)) if (auto currentType = get<TypeId>(current))
{ {
@ -561,6 +569,9 @@ std::string toString(const TypePath::Path& path, bool prefixDot)
switch (c) switch (c)
{ {
case TypePath::TypeField::Table:
result << "table";
break;
case TypePath::TypeField::Metatable: case TypePath::TypeField::Metatable:
result << "metatable"; result << "metatable";
break; break;

View File

@ -21,7 +21,6 @@ LUAU_FASTFLAGVARIABLE(LuauInstantiateInSubtyping, false)
LUAU_FASTFLAGVARIABLE(LuauTransitiveSubtyping, false) LUAU_FASTFLAGVARIABLE(LuauTransitiveSubtyping, false)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAGVARIABLE(LuauFixIndexerSubtypingOrdering, false) LUAU_FASTFLAGVARIABLE(LuauFixIndexerSubtypingOrdering, false)
LUAU_FASTFLAGVARIABLE(LuauUnifierShouldNotCopyError, false)
LUAU_FASTFLAGVARIABLE(LuauUnifierRecursionOnRestart, false) LUAU_FASTFLAGVARIABLE(LuauUnifierRecursionOnRestart, false)
namespace Luau namespace Luau
@ -2974,10 +2973,7 @@ bool Unifier::occursCheck(TypePackId needle, TypePackId haystack, bool reversed)
if (occurs) if (occurs)
{ {
reportError(location, OccursCheckFailed{}); reportError(location, OccursCheckFailed{});
if (FFlag::LuauUnifierShouldNotCopyError) log.replace(needle, BoundTypePack{builtinTypes->errorRecoveryTypePack()});
log.replace(needle, BoundTypePack{builtinTypes->errorRecoveryTypePack()});
else
log.replace(needle, *builtinTypes->errorRecoveryTypePack());
} }
return occurs; return occurs;

View File

@ -146,7 +146,7 @@ private:
AstStat* parseTypeAlias(const Location& start, bool exported); AstStat* parseTypeAlias(const Location& start, bool exported);
// type function Name ... end // type function Name ... end
AstStat* parseTypeFunction(const Location& start); AstStat* parseTypeFunction(const Location& start, bool exported);
AstDeclaredClassProp parseDeclaredClassMethod(); AstDeclaredClassProp parseDeclaredClassMethod();
@ -423,6 +423,7 @@ private:
MatchLexeme endMismatchSuspect; MatchLexeme endMismatchSuspect;
std::vector<Function> functionStack; std::vector<Function> functionStack;
size_t typeFunctionDepth = 0;
DenseHashMap<AstName, AstLocal*> localMap; DenseHashMap<AstName, AstLocal*> localMap;
std::vector<AstLocal*> localStack; std::vector<AstLocal*> localStack;

View File

@ -19,7 +19,7 @@ LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100)
LUAU_FASTFLAGVARIABLE(LuauSolverV2, false) LUAU_FASTFLAGVARIABLE(LuauSolverV2, false)
LUAU_FASTFLAGVARIABLE(LuauNativeAttribute, false) LUAU_FASTFLAGVARIABLE(LuauNativeAttribute, false)
LUAU_FASTFLAGVARIABLE(LuauAttributeSyntaxFunExpr, false) LUAU_FASTFLAGVARIABLE(LuauAttributeSyntaxFunExpr, false)
LUAU_FASTFLAGVARIABLE(LuauUserDefinedTypeFunctionsSyntax, false) LUAU_FASTFLAGVARIABLE(LuauUserDefinedTypeFunctionsSyntax2, false)
LUAU_FASTFLAGVARIABLE(LuauAllowFragmentParsing, false) LUAU_FASTFLAGVARIABLE(LuauAllowFragmentParsing, false)
namespace Luau namespace Luau
@ -901,10 +901,10 @@ AstStat* Parser::parseReturn()
AstStat* Parser::parseTypeAlias(const Location& start, bool exported) AstStat* Parser::parseTypeAlias(const Location& start, bool exported)
{ {
// parsing a type function // parsing a type function
if (FFlag::LuauUserDefinedTypeFunctionsSyntax) if (FFlag::LuauUserDefinedTypeFunctionsSyntax2)
{ {
if (lexer.current().type == Lexeme::ReservedFunction) if (lexer.current().type == Lexeme::ReservedFunction)
return parseTypeFunction(start); return parseTypeFunction(start, exported);
} }
// parsing a type alias // parsing a type alias
@ -927,11 +927,14 @@ AstStat* Parser::parseTypeAlias(const Location& start, bool exported)
} }
// type function Name `(' arglist `)' `=' funcbody `end' // type function Name `(' arglist `)' `=' funcbody `end'
AstStat* Parser::parseTypeFunction(const Location& start) AstStat* Parser::parseTypeFunction(const Location& start, bool exported)
{ {
Lexeme matchFn = lexer.current(); Lexeme matchFn = lexer.current();
nextLexeme(); nextLexeme();
if (exported)
report(start, "Type function cannot be exported");
// parse the name of the type function // parse the name of the type function
std::optional<Name> fnName = parseNameOpt("type function name"); std::optional<Name> fnName = parseNameOpt("type function name");
if (!fnName) if (!fnName)
@ -939,8 +942,13 @@ AstStat* Parser::parseTypeFunction(const Location& start)
matchRecoveryStopOnToken[Lexeme::ReservedEnd]++; matchRecoveryStopOnToken[Lexeme::ReservedEnd]++;
size_t oldTypeFunctionDepth = typeFunctionDepth;
typeFunctionDepth = functionStack.size();
AstExprFunction* body = parseFunctionBody(/* hasself */ false, matchFn, fnName->name, nullptr, AstArray<AstAttr*>({nullptr, 0})).first; AstExprFunction* body = parseFunctionBody(/* hasself */ false, matchFn, fnName->name, nullptr, AstArray<AstAttr*>({nullptr, 0})).first;
typeFunctionDepth = oldTypeFunctionDepth;
matchRecoveryStopOnToken[Lexeme::ReservedEnd]--; matchRecoveryStopOnToken[Lexeme::ReservedEnd]--;
return allocator.alloc<AstStatTypeFunction>(Location(start, body->location), fnName->name, fnName->location, body); 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; 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); 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 #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() std::optional<std::string> getCurrentWorkingDirectory()
{ {
// 2^17 - derived from the Windows path length limit // 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 #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) bool isDirectory(const std::string& path)
{ {
#ifdef _WIN32 #ifdef _WIN32

View File

@ -16,7 +16,7 @@ std::optional<std::string> readFile(const std::string& name);
std::optional<std::string> readStdin(); std::optional<std::string> readStdin();
bool isAbsolutePath(std::string_view path); 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 isDirectory(const std::string& path);
bool traverseDirectory(const std::string& path, const std::function<void(const std::string& name)>& callback); 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) if (resolvedRequire.status == RequireResolver::ModuleStatus::Cached)
return finishrequire(L); 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) else if (resolvedRequire.status == RequireResolver::ModuleStatus::NotFound)
luaL_errorL(L, "error requiring module"); 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(), '\\', '/'); std::replace(pathToResolve.begin(), pathToResolve.end(), '\\', '/');
if (!isPrefixValid())
luaL_argerrorL(L, 1, "require path must start with a valid prefix: ./, ../, or @");
substituteAliasIfPresent(pathToResolve); substituteAliasIfPresent(pathToResolve);
} }
@ -44,44 +47,14 @@ RequireResolver::ModuleStatus RequireResolver::findModule()
// Put _MODULES table on stack for checking and saving to the cache // Put _MODULES table on stack for checking and saving to the cache
luaL_findtable(L, LUA_REGISTRYINDEX, "_MODULES", 1); luaL_findtable(L, LUA_REGISTRYINDEX, "_MODULES", 1);
RequireResolver::ModuleStatus moduleStatus = findModuleImpl(); return 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;
} }
RequireResolver::ModuleStatus RequireResolver::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"}; static const std::array<const char*, 4> possibleSuffixes = {".luau", ".lua", "/init.luau", "/init.lua"};
size_t unsuffixedAbsolutePathSize = absolutePath.size(); size_t unsuffixedAbsolutePathSize = absolutePath.size();
@ -113,15 +86,34 @@ RequireResolver::ModuleStatus RequireResolver::findModuleImpl()
return ModuleStatus::NotFound; 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) bool RequireResolver::isRequireAllowed(std::string_view sourceChunkname)
{ {
LUAU_ASSERT(!sourceChunkname.empty()); LUAU_ASSERT(!sourceChunkname.empty());
return (sourceChunkname[0] == '=' || sourceChunkname[0] == '@'); 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() void RequireResolver::resolveAndStoreDefaultPaths()
@ -283,24 +275,10 @@ void RequireResolver::parseConfigInDirectory(const std::string& directory)
{ {
std::string configPath = joinPaths(directory, Luau::kConfigName); std::string configPath = joinPaths(directory, Luau::kConfigName);
size_t numPaths = config.paths.size();
if (std::optional<std::string> contents = readFile(configPath)) if (std::optional<std::string> contents = readFile(configPath))
{ {
std::optional<std::string> error = Luau::parseConfig(*contents, config); std::optional<std::string> error = Luau::parseConfig(*contents, config);
if (error) if (error)
luaL_errorL(L, "error parsing %s (%s)", configPath.c_str(), (*error).c_str()); 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, Cached,
FileRead, FileRead,
Ambiguous,
NotFound NotFound
}; };
@ -46,10 +47,11 @@ private:
bool isConfigFullyResolved = false; bool isConfigFullyResolved = false;
bool isRequireAllowed(std::string_view sourceChunkname); bool isRequireAllowed(std::string_view sourceChunkname);
bool shouldSearchPathsArray(); bool isPrefixValid();
void resolveAndStoreDefaultPaths(); void resolveAndStoreDefaultPaths();
ModuleStatus findModuleImpl(); ModuleStatus findModuleImpl();
bool isPathAmbiguous(const std::string& path);
std::optional<std::string> getRequiringContextAbsolute(); std::optional<std::string> getRequiringContextAbsolute();
std::string getRequiringContextRelative(); std::string getRequiringContextRelative();

View File

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

View File

@ -32,7 +32,6 @@ struct Config
std::vector<std::string> globals; std::vector<std::string> globals;
std::vector<std::string> paths;
std::unordered_map<std::string, std::string> aliases; 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); config.globals.push_back(value);
return std::nullopt; 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") else if (keys.size() == 2 && keys[0] == "aliases")
return parseAlias(config.aliases, keys[1], value); return parseAlias(config.aliases, keys[1], value);
else if (compat && keys.size() == 2 && keys[0] == "language" && keys[1] == "mode") else if (compat && keys.size() == 2 && keys[0] == "language" && keys[1] == "mode")

View File

@ -8,6 +8,8 @@
using namespace Luau; using namespace Luau;
LUAU_FASTFLAG(LuauDocumentationAtPosition)
struct DocumentationSymbolFixture : BuiltinsFixture struct DocumentationSymbolFixture : BuiltinsFixture
{ {
std::optional<DocumentationSymbol> getDocSymbol(const std::string& source, Position position) 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"); 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_END();
TEST_SUITE_BEGIN("AstQuery"); TEST_SUITE_BEGIN("AstQuery");

View File

@ -21,6 +21,7 @@ LUAU_FASTINT(LuauCompileInlineThresholdMaxBoost)
LUAU_FASTINT(LuauCompileLoopUnrollThreshold) LUAU_FASTINT(LuauCompileLoopUnrollThreshold)
LUAU_FASTINT(LuauCompileLoopUnrollThresholdMaxBoost) LUAU_FASTINT(LuauCompileLoopUnrollThresholdMaxBoost)
LUAU_FASTINT(LuauRecursionLimit) LUAU_FASTINT(LuauRecursionLimit)
LUAU_FASTFLAG(LuauUserDefinedTypeFunctionsSyntax2)
using namespace Luau; using namespace Luau;
@ -2796,6 +2797,16 @@ TEST_CASE("TypeAliasing")
CHECK_NOTHROW(Luau::compileOrThrow(bcb, "type A = number local a: A = 1", options, parseOptions)); 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") TEST_CASE("DebugLineInfo")
{ {
Luau::BytecodeBuilder bcb; Luau::BytecodeBuilder bcb;

View File

@ -25,6 +25,7 @@ void ConstraintGeneratorFixture::generateConstraints(const std::string& code)
cg = std::make_unique<ConstraintGenerator>( cg = std::make_unique<ConstraintGenerator>(
mainModule, mainModule,
NotNull{&normalizer}, NotNull{&normalizer},
NotNull{&typeFunctionRuntime},
NotNull(&moduleResolver), NotNull(&moduleResolver),
builtinTypes, builtinTypes,
NotNull(&ice), NotNull(&ice),

View File

@ -20,7 +20,8 @@ struct ConstraintGeneratorFixture : Fixture
DcrLogger logger; DcrLogger logger;
UnifierSharedState sharedState{&ice}; UnifierSharedState sharedState{&ice};
Normalizer normalizer{&arena, builtinTypes, NotNull{&sharedState}}; Normalizer normalizer{&arena, builtinTypes, NotNull{&sharedState}};
TypeFunctionRuntime typeFunctionRuntime; TypeCheckLimits limits;
TypeFunctionRuntime typeFunctionRuntime{NotNull{&ice}, NotNull{&limits}};
std::unique_ptr<DataFlowGraph> dfg; std::unique_ptr<DataFlowGraph> dfg;
std::unique_ptr<ConstraintGenerator> cg; std::unique_ptr<ConstraintGenerator> cg;

View File

@ -598,6 +598,72 @@ BuiltinsFixture::BuiltinsFixture(bool freeze, bool prepareAutocomplete)
Luau::freeze(frontend.globalsForAutocomplete.globalTypes); 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) ModuleName fromString(std::string_view name)
{ {
return ModuleName(name); return ModuleName(name);

View File

@ -20,8 +20,10 @@
#include "doctest.h" #include "doctest.h"
#include <string> #include <string>
#include <string_view>
#include <unordered_map> #include <unordered_map>
#include <optional> #include <optional>
#include <vector>
namespace Luau namespace Luau
{ {
@ -159,6 +161,9 @@ struct BuiltinsFixture : Fixture
BuiltinsFixture(bool freeze = true, bool prepareAutocomplete = false); 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); ModuleName fromString(std::string_view name);
template<typename T> template<typename T>

View File

@ -11,7 +11,6 @@
#include "Luau/BuiltinDefinitions.h" #include "Luau/BuiltinDefinitions.h"
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauNormalizeNotUnknownIntersection)
LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit)
using namespace Luau; 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") TEST_CASE_FIXTURE(NormalizeFixture, "intersect_with_not_unknown")
{ {
ScopedFastFlag sff{FFlag::LuauNormalizeNotUnknownIntersection, true};
TypeId notUnknown = arena.addType(NegationType{builtinTypes->unknownType}); TypeId notUnknown = arena.addType(NegationType{builtinTypes->unknownType});
TypeId type = arena.addType(IntersectionType{{builtinTypes->numberType, notUnknown}}); TypeId type = arena.addType(IntersectionType{{builtinTypes->numberType, notUnknown}});
std::shared_ptr<const NormalizedType> normalized = normalizer.normalize(type); std::shared_ptr<const NormalizedType> normalized = normalizer.normalize(type);

View File

@ -17,7 +17,7 @@ LUAU_FASTINT(LuauTypeLengthLimit)
LUAU_FASTINT(LuauParseErrorLimit) LUAU_FASTINT(LuauParseErrorLimit)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauAttributeSyntaxFunExpr) LUAU_FASTFLAG(LuauAttributeSyntaxFunExpr)
LUAU_FASTFLAG(LuauUserDefinedTypeFunctionsSyntax) LUAU_FASTFLAG(LuauUserDefinedTypeFunctionsSyntax2)
namespace namespace
{ {
@ -2380,7 +2380,7 @@ TEST_CASE_FIXTURE(Fixture, "invalid_type_forms")
TEST_CASE_FIXTURE(Fixture, "parse_user_defined_type_functions") TEST_CASE_FIXTURE(Fixture, "parse_user_defined_type_functions")
{ {
ScopedFastFlag sff{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; ScopedFastFlag sff{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
AstStat* stat = parse(R"( AstStat* stat = parse(R"(
type function foo() type function foo()
@ -2394,6 +2394,38 @@ TEST_CASE_FIXTURE(Fixture, "parse_user_defined_type_functions")
REQUIRE(f->name == "foo"); 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_END();
TEST_SUITE_BEGIN("ParseErrorRecovery"); TEST_SUITE_BEGIN("ParseErrorRecovery");

View File

@ -308,6 +308,22 @@ TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireInitLua")
assertOutputContainsAll({"true", "result from init.lua"}); 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") TEST_CASE_FIXTURE(ReplWithPathFixture, "CheckCacheAfterRequireLuau")
{ {
std::string relativePath = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/module"; 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"}); 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); runProtectedRequire(path);
assertOutputContainsAll({"true", "result from library"}); assertOutputContainsAll({"false", "require path must start with a valid prefix: ./, ../, or @"});
}
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"});
} }
TEST_CASE_FIXTURE(ReplWithPathFixture, "RequirePathWithAlias") TEST_CASE_FIXTURE(ReplWithPathFixture, "RequirePathWithAlias")

View File

@ -66,7 +66,8 @@ struct SubtypeFixture : Fixture
InternalErrorReporter iceReporter; InternalErrorReporter iceReporter;
UnifierSharedState sharedState{&ice}; UnifierSharedState sharedState{&ice};
Normalizer normalizer{&arena, builtinTypes, NotNull{&sharedState}}; Normalizer normalizer{&arena, builtinTypes, NotNull{&sharedState}};
TypeFunctionRuntime typeFunctionRuntime; TypeCheckLimits limits;
TypeFunctionRuntime typeFunctionRuntime{NotNull{&iceReporter}, NotNull{&limits}};
ScopedFastFlag sff{FFlag::LuauSolverV2, true}; ScopedFastFlag sff{FFlag::LuauSolverV2, true};

View File

@ -13,7 +13,7 @@ using namespace Luau;
LUAU_FASTFLAG(LuauRecursiveTypeParameterRestriction); LUAU_FASTFLAG(LuauRecursiveTypeParameterRestriction);
LUAU_FASTFLAG(LuauSolverV2); LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTFLAG(LuauAttributeSyntax); LUAU_FASTFLAG(LuauAttributeSyntax);
LUAU_FASTFLAG(LuauUserDefinedTypeFunctions) LUAU_FASTFLAG(LuauUserDefinedTypeFunctions2)
TEST_SUITE_BEGIN("ToString"); 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 std::vector<TypeId>{builtinTypes->numberType}, // Type Function Arguments
{}, {},
{AstName{"woohoo"}}, // Type Function Name {AstName{"woohoo"}}, // Type Function Name
std::nullopt
}; };
Type tv{tftt}; Type tv{tftt};
if (FFlag::LuauSolverV2 && FFlag::LuauUserDefinedTypeFunctions) if (FFlag::LuauSolverV2 && FFlag::LuauUserDefinedTypeFunctions2)
CHECK_EQ(toString(&tv, {}), "woohoo<number>"); CHECK_EQ(toString(&tv, {}), "woohoo<number>");
} }

View File

@ -12,7 +12,7 @@
using namespace Luau; using namespace Luau;
LUAU_FASTFLAG(LuauUserDefinedTypeFunctionsSyntax) LUAU_FASTFLAG(LuauUserDefinedTypeFunctionsSyntax2)
TEST_SUITE_BEGIN("TranspilerTests"); TEST_SUITE_BEGIN("TranspilerTests");
@ -698,7 +698,7 @@ TEST_CASE_FIXTURE(Fixture, "transpile_string_literal_escape")
TEST_CASE_FIXTURE(Fixture, "transpile_type_functions") 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 )"; 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; using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauUserDefinedTypeFunctions) LUAU_FASTFLAG(LuauUserDefinedTypeFunctions2)
LUAU_DYNAMIC_FASTINT(LuauTypeFamilyApplicationCartesianProductLimit) LUAU_DYNAMIC_FASTINT(LuauTypeFamilyApplicationCartesianProductLimit)
struct TypeFunctionFixture : Fixture 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'"); 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(); TEST_SUITE_END();

View File

@ -8,16 +8,17 @@
using namespace Luau; using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauUserDefinedTypeFunctionsSyntax) LUAU_FASTFLAG(LuauUserDefinedTypeFunctionsSyntax2)
LUAU_FASTFLAG(LuauUserDefinedTypeFunctions) LUAU_FASTFLAG(LuauUserDefinedTypeFunctions2)
LUAU_FASTFLAG(LuauUserDefinedTypeFunctionNoEvaluation)
TEST_SUITE_BEGIN("UserDefinedTypeFunctionTests"); TEST_SUITE_BEGIN("UserDefinedTypeFunctionTests");
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_nil_serialization_works") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_nil_serialization_works")
{ {
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
CheckResult result = check(R"( CheckResult result = check(R"(
type function serialize_nil(arg) 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") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_nil_methods_work")
{ {
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
CheckResult result = check(R"( CheckResult result = check(R"(
type function getnil() type function getnil()
@ -54,8 +55,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_nil_methods_work")
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_unknown_serialization_works") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_unknown_serialization_works")
{ {
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
CheckResult result = check(R"( CheckResult result = check(R"(
type function serialize_unknown(arg) 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") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_unknown_methods_work")
{ {
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
CheckResult result = check(R"( CheckResult result = check(R"(
type function getunknown() type function getunknown()
@ -92,8 +93,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_unknown_methods_work")
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_never_serialization_works") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_never_serialization_works")
{ {
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
CheckResult result = check(R"( CheckResult result = check(R"(
type function serialize_never(arg) 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") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_never_methods_work")
{ {
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
CheckResult result = check(R"( CheckResult result = check(R"(
type function getnever() type function getnever()
@ -130,8 +131,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_never_methods_work")
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_any_serialization_works") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_any_serialization_works")
{ {
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
CheckResult result = check(R"( CheckResult result = check(R"(
type function serialize_any(arg) 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") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_any_methods_work")
{ {
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
CheckResult result = check(R"( CheckResult result = check(R"(
type function getany() type function getany()
@ -168,8 +169,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_any_methods_work")
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_boolean_serialization_works") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_boolean_serialization_works")
{ {
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
CheckResult result = check(R"( CheckResult result = check(R"(
type function serialize_bool(arg) 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") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_boolean_methods_work")
{ {
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
CheckResult result = check(R"( CheckResult result = check(R"(
type function getboolean() type function getboolean()
@ -206,8 +207,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_boolean_methods_work")
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_number_serialization_works") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_number_serialization_works")
{ {
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
CheckResult result = check(R"( CheckResult result = check(R"(
type function serialize_num(arg) 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") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_number_methods_work")
{ {
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
CheckResult result = check(R"( CheckResult result = check(R"(
type function getnumber() type function getnumber()
@ -244,8 +245,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_number_methods_work")
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_string_serialization_works") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_string_serialization_works")
{ {
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
CheckResult result = check(R"( CheckResult result = check(R"(
type function serialize_str(arg) 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") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_string_methods_work")
{ {
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
CheckResult result = check(R"( CheckResult result = check(R"(
type function getstring() type function getstring()
@ -282,8 +283,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_string_methods_work")
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_boolsingleton_serialization_works") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_boolsingleton_serialization_works")
{ {
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
CheckResult result = check(R"( CheckResult result = check(R"(
type function serialize_boolsingleton(arg) 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") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_boolsingleton_methods_work")
{ {
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
CheckResult result = check(R"( CheckResult result = check(R"(
type function getboolsingleton() type function getboolsingleton()
@ -320,8 +321,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_boolsingleton_methods_work")
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_strsingleton_serialization_works") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_strsingleton_serialization_works")
{ {
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
CheckResult result = check(R"( CheckResult result = check(R"(
type function serialize_strsingleton(arg) 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") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_strsingleton_methods_work")
{ {
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
CheckResult result = check(R"( CheckResult result = check(R"(
type function getstrsingleton() type function getstrsingleton()
@ -358,8 +359,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_strsingleton_methods_work")
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_union_serialization_works") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_union_serialization_works")
{ {
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
CheckResult result = check(R"( CheckResult result = check(R"(
type function serialize_union(arg) 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") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_union_methods_work")
{ {
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
CheckResult result = check(R"( CheckResult result = check(R"(
type function getunion() type function getunion()
@ -409,8 +410,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_union_methods_work")
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_intersection_serialization_works") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_intersection_serialization_works")
{ {
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
CheckResult result = check(R"( CheckResult result = check(R"(
type function serialize_intersection(arg) 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") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_intersection_methods_work")
{ {
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
CheckResult result = check(R"( CheckResult result = check(R"(
type function getintersection() type function getintersection()
@ -466,8 +467,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_intersection_methods_work")
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_negation_methods_work") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_negation_methods_work")
{ {
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
CheckResult result = check(R"( CheckResult result = check(R"(
type function getnegation() type function getnegation()
@ -492,8 +493,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_negation_methods_work")
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_table_serialization_works") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_table_serialization_works")
{ {
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
CheckResult result = check(R"( CheckResult result = check(R"(
type function serialize_table(arg) 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") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_table_methods_work")
{ {
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
CheckResult result = check(R"( CheckResult result = check(R"(
type function gettable() type function gettable()
@ -553,8 +554,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_table_methods_work")
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_metatable_methods_work") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_metatable_methods_work")
{ {
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
CheckResult result = check(R"( CheckResult result = check(R"(
type function getmetatable() type function getmetatable()
@ -587,8 +588,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_metatable_methods_work")
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_function_serialization_works") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_function_serialization_works")
{ {
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
CheckResult result = check(R"( CheckResult result = check(R"(
type function serialize_func(arg) 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") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_function_methods_work")
{ {
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
CheckResult result = check(R"( CheckResult result = check(R"(
type function getfunction() type function getfunction()
@ -635,8 +636,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_function_methods_work")
TEST_CASE_FIXTURE(ClassFixture, "udtf_class_serialization_works") TEST_CASE_FIXTURE(ClassFixture, "udtf_class_serialization_works")
{ {
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
CheckResult result = check(R"( CheckResult result = check(R"(
type function serialize_class(arg) 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") TEST_CASE_FIXTURE(ClassFixture, "udtf_class_methods_works")
{ {
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
CheckResult result = check(R"( CheckResult result = check(R"(
@ -675,8 +676,8 @@ TEST_CASE_FIXTURE(ClassFixture, "udtf_class_methods_works")
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_check_mutability") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_check_mutability")
{ {
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
CheckResult result = check(R"( CheckResult result = check(R"(
type function checkmut() type function checkmut()
@ -708,8 +709,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_check_mutability")
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_copy_works") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_copy_works")
{ {
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
CheckResult result = check(R"( CheckResult result = check(R"(
type function getcopy() type function getcopy()
@ -742,8 +743,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_copy_works")
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_simple_cyclic_serialization_works") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_simple_cyclic_serialization_works")
{ {
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
CheckResult result = check(R"( CheckResult result = check(R"(
type function serialize_cycle(arg) 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") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_createtable_bad_metatable")
{ {
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
CheckResult result = check(R"( CheckResult result = check(R"(
type function badmetatable() type function badmetatable()
@ -786,8 +787,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_createtable_bad_metatable")
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_complex_cyclic_serialization_works") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_complex_cyclic_serialization_works")
{ {
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
CheckResult result = check(R"( CheckResult result = check(R"(
type function serialize_cycle2(arg) 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") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_user_error_is_reported")
{ {
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
CheckResult result = check(R"( CheckResult result = check(R"(
type function errors_if_string(arg) 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") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_type_overrides_call_metamethod")
{ {
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
CheckResult result = check(R"( CheckResult result = check(R"(
type function hello(arg) 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") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_type_overrides_eq_metamethod")
{ {
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
CheckResult result = check(R"( CheckResult result = check(R"(
type function hello() 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") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_function_type_cant_call_get_props")
{ {
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
CheckResult result = check(R"( CheckResult result = check(R"(
type function hello(arg) 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 newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
CheckResult result = check(R"( CheckResult result = check(R"(
type function foo() type function foo()
return "hi" return "hi"
end end
local x = true; type function bar()
type function cannot_call_others() return types.singleton(foo())
return foo()
end 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 LUAU_CHECK_ERROR_COUNT(1, result);
UserDefinedTypeFunctionError* e = get<UserDefinedTypeFunctionError>(result.errors[0]); TypePackMismatch* tpm = get<TypePackMismatch>(result.errors[0]);
REQUIRE(e); REQUIRE(tpm);
CHECK(e->message == "'cannot_call_others' type function errored at runtime: [string \"cannot_call_others\"]:7: attempt to call a nil value"); 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") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_optionify")
{ {
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
CheckResult result = check(R"( CheckResult result = check(R"(
type function optionify(tbl) type function optionify(tbl)
@ -959,8 +988,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_optionify")
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_calling_illegal_global") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_calling_illegal_global")
{ {
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
CheckResult result = check(R"( CheckResult result = check(R"(
type function illegal(arg) type function illegal(arg)
@ -980,9 +1009,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_calling_illegal_global")
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_recursion_and_gc") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_recursion_and_gc")
{ {
ScopedFastFlag newSolver{ FFlag::LuauSolverV2, true }; ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag udtfSyntax{ FFlag::LuauUserDefinedTypeFunctionsSyntax, true }; ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
ScopedFastFlag udtf{ FFlag::LuauUserDefinedTypeFunctions, true }; ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true};
CheckResult result = check(R"( CheckResult result = check(R"(
type function foo(tbl) type function foo(tbl)
@ -1004,4 +1033,72 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_recursion_and_gc")
REQUIRE(tpm); 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(); TEST_SUITE_END();

View File

@ -9,8 +9,8 @@
using namespace Luau; using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauUserDefinedTypeFunctionsSyntax) LUAU_FASTFLAG(LuauUserDefinedTypeFunctionsSyntax2)
LUAU_FASTFLAG(LuauUserDefinedTypeFunctions) LUAU_FASTFLAG(LuauUserDefinedTypeFunctions2)
TEST_SUITE_BEGIN("TypeAliases"); 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") TEST_CASE_FIXTURE(BuiltinsFixture, "type_alias_adds_reduce_constraint_for_type_function")
{ {
if (!FFlag::LuauSolverV2 || !FFlag::LuauUserDefinedTypeFunctions) if (!FFlag::LuauSolverV2 || !FFlag::LuauUserDefinedTypeFunctions2)
return; return;
CheckResult result = check(R"( 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") TEST_CASE_FIXTURE(Fixture, "user_defined_type_function_errors")
{ {
ScopedFastFlag sff{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; ScopedFastFlag sff{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true};
ScopedFastFlag noUDTFimpl{FFlag::LuauUserDefinedTypeFunctions, false}; ScopedFastFlag noUDTFimpl{FFlag::LuauUserDefinedTypeFunctions2, false};
CheckResult result = check(R"( CheckResult result = check(R"(
type function foo() 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"); 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(); TEST_SUITE_END();

View File

@ -20,8 +20,6 @@ LUAU_FASTFLAG(LuauInstantiateInSubtyping);
LUAU_FASTFLAG(LuauSolverV2); LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTINT(LuauTarjanChildLimit); LUAU_FASTINT(LuauTarjanChildLimit);
LUAU_DYNAMIC_FASTFLAG(LuauImproveNonFunctionCallError)
TEST_SUITE_BEGIN("TypeInferFunctions"); TEST_SUITE_BEGIN("TypeInferFunctions");
TEST_CASE_FIXTURE(Fixture, "general_case_table_literal_blocks") 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); 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 }");
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 |}");
}
else 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 non-function { x: number } & { y: string }");
else
CHECK_EQ(toString(result.errors[0]), "Cannot call non-function {| x: number |}");
}
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "attempt_to_call_an_intersection_of_tables_with_call_metamethod") 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); 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.)"; We are unable to determine the appropriate result type for such a call.)";
CHECK(expected == toString(result.errors[0])); CHECK(expected == toString(result.errors[0]));
}
else
CHECK("Cannot call non-function (() -> () -> ()) | (() -> ())" == toString(result.errors[0]));
} }
TEST_CASE_FIXTURE(Fixture, "fuzzer_missing_follow_in_ast_stat_fun") TEST_CASE_FIXTURE(Fixture, "fuzzer_missing_follow_in_ast_stat_fun")

View File

@ -16,8 +16,6 @@ using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_DYNAMIC_FASTFLAG(LuauImproveNonFunctionCallError)
TEST_SUITE_BEGIN("TypeInferLoops"); TEST_SUITE_BEGIN("TypeInferLoops");
TEST_CASE_FIXTURE(Fixture, "for_loop") 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); LUAU_REQUIRE_ERROR_COUNT(1, result);
if (DFFlag::LuauImproveNonFunctionCallError) CHECK_EQ("Cannot call a value of type string", toString(result.errors[0]));
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]));
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_with_just_one_iterator_is_ok") 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(); TEST_SUITE_END();

View File

@ -17,7 +17,6 @@
using namespace Luau; using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauRemoveBadRelationalOperatorWarning)
TEST_SUITE_BEGIN("TypeInferOperators"); 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 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); LUAU_REQUIRE_NO_ERRORS(result);
return; 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 // 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. // implemented in the new solver, so assert there are no errors.
if (FFlag::LuauRemoveBadRelationalOperatorWarning || FFlag::LuauSolverV2) LUAU_REQUIRE_NO_ERRORS(result);
LUAU_REQUIRE_NO_ERRORS(result);
else
LUAU_REQUIRE_ERROR_COUNT(1, result);
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "no_infinite_expansion_of_free_type" * doctest::timeout(1.0)) 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(); TEST_SUITE_END();

View File

@ -20,7 +20,6 @@ LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAG(LuauFixIndexerSubtypingOrdering) LUAU_FASTFLAG(LuauFixIndexerSubtypingOrdering)
LUAU_FASTFLAG(LuauAcceptIndexingTableUnionsIntersections) LUAU_FASTFLAG(LuauAcceptIndexingTableUnionsIntersections)
LUAU_DYNAMIC_FASTFLAG(LuauImproveNonFunctionCallError)
LUAU_DYNAMIC_FASTINT(LuauTypeSolverRelease) LUAU_DYNAMIC_FASTINT(LuauTypeSolverRelease)
TEST_SUITE_BEGIN("TableTests"); TEST_SUITE_BEGIN("TableTests");
@ -2407,7 +2406,7 @@ could not be converted into
// //
// Second, nil <: unknown, so we consider that parameter to be optional. // Second, nil <: unknown, so we consider that parameter to be optional.
LUAU_REQUIRE_ERROR_COUNT(1, result); 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) else if (FFlag::LuauInstantiateInSubtyping)
{ {
@ -2583,10 +2582,7 @@ b()
LUAU_REQUIRE_ERROR_COUNT(1, result); 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 }, { } })");
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 }, { } })");
} }
TEST_CASE_FIXTURE(Fixture, "table_subtyping_shouldn't_add_optional_properties_to_sealed_tables") 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); 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{ TypeError e{
Location{{5, 20}, {5, 21}}, Location{{5, 20}, {5, 21}},
@ -3273,14 +3273,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_call_metamethod_must_be_callable")
}; };
CHECK(result.errors[0] == e); 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") 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); 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(); TEST_SUITE_END();

View File

@ -235,6 +235,23 @@ TEST_CASE_FIXTURE(ClassFixture, "metatables")
} }
SUBCASE("table") 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"( TYPESOLVE_CODE(R"(
local mt = { foo = 123 } local mt = { foo = 123 }

View File

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

View File

@ -1,5 +1,4 @@
{ {
"paths": ["../ProjectLuauLibraries"],
"aliases": { "aliases": {
"dep": "dependency", "dep": "dependency",
"subdir": "subdirectory" "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" result[#result+1] = "required into module"
return result return result