From 543de6e9395e3884e20c6a454cc9e93a65045588 Mon Sep 17 00:00:00 2001 From: Andy Friesen Date: Fri, 4 Oct 2024 11:29:55 -0700 Subject: [PATCH] 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 Co-authored-by: Hunter Goldstein Co-authored-by: Varun Saini Co-authored-by: Vyacheslav Egorov --- Analysis/include/Luau/ConstraintGenerator.h | 7 + Analysis/include/Luau/Frontend.h | 15 - Analysis/include/Luau/ModuleResolver.h | 2 - Analysis/include/Luau/NonStrictTypeChecker.h | 2 + Analysis/include/Luau/Type.h | 9 +- Analysis/include/Luau/TypeChecker2.h | 4 +- Analysis/include/Luau/TypeFunction.h | 22 +- Analysis/include/Luau/TypePath.h | 2 + Analysis/src/AstQuery.cpp | 81 ++++- Analysis/src/Autocomplete.cpp | 5 +- Analysis/src/ConstraintGenerator.cpp | 157 +++++++++- Analysis/src/ConstraintSolver.cpp | 18 +- Analysis/src/Error.cpp | 35 +-- Analysis/src/Frontend.cpp | 88 ++---- Analysis/src/Generalization.cpp | 113 ++++++- Analysis/src/Module.cpp | 45 ++- Analysis/src/NonStrictTypeChecker.cpp | 25 +- Analysis/src/Normalize.cpp | 27 +- Analysis/src/Substitution.cpp | 2 +- Analysis/src/Subtyping.cpp | 13 +- Analysis/src/TypeChecker2.cpp | 17 +- Analysis/src/TypeFunction.cpp | 202 ++++++++---- Analysis/src/TypeFunctionRuntime.cpp | 30 +- Analysis/src/TypeInfer.cpp | 38 +-- Analysis/src/TypePath.cpp | 11 + Analysis/src/Unifier.cpp | 6 +- Ast/include/Luau/Parser.h | 3 +- Ast/src/Parser.cpp | 22 +- CLI/FileUtils.cpp | 20 +- CLI/FileUtils.h | 2 +- CLI/Repl.cpp | 2 + CLI/Require.cpp | 78 ++--- CLI/Require.h | 4 +- Compiler/src/Compiler.cpp | 4 + Config/include/Luau/Config.h | 1 - Config/src/Config.cpp | 5 - tests/AstQuery.test.cpp | 40 +++ tests/Compiler.test.cpp | 11 + tests/ConstraintGeneratorFixture.cpp | 1 + tests/ConstraintGeneratorFixture.h | 3 +- tests/Fixture.cpp | 66 ++++ tests/Fixture.h | 5 + tests/Normalize.test.cpp | 3 - tests/Parser.test.cpp | 36 ++- tests/RequireByString.test.cpp | 36 ++- tests/Subtyping.test.cpp | 3 +- tests/ToString.test.cpp | 5 +- tests/Transpiler.test.cpp | 4 +- tests/TypeFunction.test.cpp | 18 +- tests/TypeFunction.user.test.cpp | 293 ++++++++++++------ tests/TypeInfer.aliases.test.cpp | 24 +- tests/TypeInfer.functions.test.cpp | 27 +- tests/TypeInfer.loops.test.cpp | 7 +- tests/TypeInfer.modules.test.cpp | 88 ++++++ tests/TypeInfer.operators.test.cpp | 8 +- tests/TypeInfer.refinements.test.cpp | 53 ++++ tests/TypeInfer.tables.test.cpp | 45 ++- tests/TypePath.test.cpp | 17 + tests/require/with_config/.luaurc | 1 - tests/require/with_config/src/.luaurc | 1 - .../with_config/src/fail_requirer.luau | 2 - .../src/global_library_requirer.luau | 2 - tests/require/with_config/src/requirer.luau | 2 - .../ambiguous/directory/dependency.luau | 1 + .../ambiguous/directory/dependency/init.luau | 1 + .../ambiguous/file/dependency.lua | 1 + .../ambiguous/file/dependency.luau | 1 + .../ambiguous_directory_requirer.luau | 3 + .../ambiguous_file_requirer.luau | 3 + tests/require/without_config/module.luau | 2 +- 70 files changed, 1391 insertions(+), 539 deletions(-) delete mode 100644 tests/require/with_config/src/fail_requirer.luau delete mode 100644 tests/require/with_config/src/global_library_requirer.luau delete mode 100644 tests/require/with_config/src/requirer.luau create mode 100644 tests/require/without_config/ambiguous/directory/dependency.luau create mode 100644 tests/require/without_config/ambiguous/directory/dependency/init.luau create mode 100644 tests/require/without_config/ambiguous/file/dependency.lua create mode 100644 tests/require/without_config/ambiguous/file/dependency.luau create mode 100644 tests/require/without_config/ambiguous_directory_requirer.luau create mode 100644 tests/require/without_config/ambiguous_file_requirer.luau diff --git a/Analysis/include/Luau/ConstraintGenerator.h b/Analysis/include/Luau/ConstraintGenerator.h index e7932a35a..600574f06 100644 --- a/Analysis/include/Luau/ConstraintGenerator.h +++ b/Analysis/include/Luau/ConstraintGenerator.h @@ -28,6 +28,7 @@ struct Scope; using ScopePtr = std::shared_ptr; struct DcrLogger; +struct TypeFunctionRuntime; struct Inference { @@ -108,6 +109,8 @@ struct ConstraintGenerator // Needed to be able to enable error-suppression preservation for immediate refinements. NotNull normalizer; + // Needed to register all available type functions for execution at later stages. + NotNull typeFunctionRuntime; // Needed to resolve modules to make 'require' import types properly. NotNull moduleResolver; // Occasionally constraint generation needs to produce an ICE. @@ -125,6 +128,7 @@ struct ConstraintGenerator ConstraintGenerator( ModulePtr module, NotNull normalizer, + NotNull typeFunctionRuntime, NotNull moduleResolver, NotNull builtinTypes, NotNull ice, @@ -223,7 +227,10 @@ struct ConstraintGenerator ); void applyRefinements(const ScopePtr& scope, Location location, RefinementId refinement); + LUAU_NOINLINE void checkAliases(const ScopePtr& scope, AstStatBlock* block); + ControlFlow visitBlockWithoutChildScope(const ScopePtr& scope, AstStatBlock* block); + ControlFlow visitBlockWithoutChildScope_DEPRECATED(const ScopePtr& scope, AstStatBlock* block); ControlFlow visit(const ScopePtr& scope, AstStat* stat); ControlFlow visit(const ScopePtr& scope, AstStatBlock* block); diff --git a/Analysis/include/Luau/Frontend.h b/Analysis/include/Luau/Frontend.h index d8a40d24d..49d7a36dd 100644 --- a/Analysis/include/Luau/Frontend.h +++ b/Analysis/include/Luau/Frontend.h @@ -44,21 +44,6 @@ struct LoadDefinitionFileResult std::optional parseMode(const std::vector& hotcomments); -std::vector parsePathExpr(const AstExpr& pathExpr); - -// Exported only for convenient testing. -std::optional pathExprToModuleName(const ModuleName& currentModuleName, const std::vector& 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 pathExprToModuleName(const ModuleName& currentModuleName, const AstExpr& expr); - struct SourceNode { bool hasDirtySourceModule() const diff --git a/Analysis/include/Luau/ModuleResolver.h b/Analysis/include/Luau/ModuleResolver.h index d892ccd7f..59751793c 100644 --- a/Analysis/include/Luau/ModuleResolver.h +++ b/Analysis/include/Luau/ModuleResolver.h @@ -20,8 +20,6 @@ struct ModuleResolver virtual ~ModuleResolver() {} /** Compute a ModuleName from an AST fragment. This AST fragment is generally the argument to the require() function. - * - * You probably want to implement this with some variation of pathExprToModuleName. * * @returns The ModuleInfo if the expression is a syntactically legal path. * @returns std::nullopt if we are unable to determine whether or not the expression is a valid path. Type inference will diff --git a/Analysis/include/Luau/NonStrictTypeChecker.h b/Analysis/include/Luau/NonStrictTypeChecker.h index 8e80c7629..6229a932c 100644 --- a/Analysis/include/Luau/NonStrictTypeChecker.h +++ b/Analysis/include/Luau/NonStrictTypeChecker.h @@ -9,11 +9,13 @@ namespace Luau { struct BuiltinTypes; +struct TypeFunctionRuntime; struct UnifierSharedState; struct TypeCheckLimits; void checkNonStrict( NotNull builtinTypes, + NotNull typeFunctionRuntime, NotNull ice, NotNull unifierState, NotNull dfg, diff --git a/Analysis/include/Luau/Type.h b/Analysis/include/Luau/Type.h index a43dbff90..3a7aefd88 100644 --- a/Analysis/include/Luau/Type.h +++ b/Analysis/include/Luau/Type.h @@ -613,20 +613,17 @@ struct TypeFunctionInstanceType std::vector packArguments; std::optional userFuncName; // Name of the user-defined type function; only available for UDTFs - std::optional userFuncBody; // Body of the user-defined type function; only available for UDTFs TypeFunctionInstanceType( NotNull function, std::vector typeArguments, std::vector packArguments, - std::optional userFuncName = std::nullopt, - std::optional userFuncBody = std::nullopt + std::optional userFuncName = std::nullopt ) : function(function) , typeArguments(typeArguments) , packArguments(packArguments) , userFuncName(userFuncName) - , userFuncBody(userFuncBody) { } @@ -1159,6 +1156,10 @@ TypeId freshType(NotNull arena, NotNull builtinTypes, S using TypeIdPredicate = std::function(TypeId)>; std::vector filterMap(TypeId type, TypeIdPredicate predicate); +// A tag to mark a type which doesn't derive directly from the root type as overriding the return of `typeof`. +// Any classes which derive from this type will have typeof return this type. +static constexpr char kTypeofRootTag[] = "typeofRoot"; + void attachTag(TypeId ty, const std::string& tagName); void attachTag(Property& prop, const std::string& tagName); diff --git a/Analysis/include/Luau/TypeChecker2.h b/Analysis/include/Luau/TypeChecker2.h index e7db9411d..3ede5ca71 100644 --- a/Analysis/include/Luau/TypeChecker2.h +++ b/Analysis/include/Luau/TypeChecker2.h @@ -60,6 +60,7 @@ struct Reasonings void check( NotNull builtinTypes, + NotNull typeFunctionRuntime, NotNull sharedState, NotNull limits, DcrLogger* logger, @@ -70,6 +71,7 @@ void check( struct TypeChecker2 { NotNull builtinTypes; + NotNull typeFunctionRuntime; DcrLogger* logger; const NotNull limits; const NotNull ice; @@ -83,12 +85,12 @@ struct TypeChecker2 DenseHashSet seenTypeFunctionInstances{nullptr}; Normalizer normalizer; - TypeFunctionRuntime typeFunctionRuntime; Subtyping _subtyping; NotNull subtyping; TypeChecker2( NotNull builtinTypes, + NotNull typeFunctionRuntime, NotNull unifierState, NotNull limits, DcrLogger* logger, diff --git a/Analysis/include/Luau/TypeFunction.h b/Analysis/include/Luau/TypeFunction.h index 252b4c9af..df696b62f 100644 --- a/Analysis/include/Luau/TypeFunction.h +++ b/Analysis/include/Luau/TypeFunction.h @@ -12,6 +12,8 @@ #include #include +struct lua_State; + namespace Luau { @@ -20,11 +22,30 @@ struct TxnLog; struct ConstraintSolver; class Normalizer; +using StateRef = std::unique_ptr; + struct TypeFunctionRuntime { + TypeFunctionRuntime(NotNull ice, NotNull limits); + ~TypeFunctionRuntime(); + + // Return value is an error message if registration failed + std::optional registerFunction(AstStatTypeFunction* function); + // For user-defined type functions, we store all generated types and packs for the duration of the typecheck TypedAllocator typeArena; TypedAllocator typePackArena; + + NotNull ice; + NotNull limits; + + StateRef state; + + // Evaluation of type functions should only be performed in the absence of parse errors in the source module + bool allowEvaluation = true; + +private: + void prepareState(); }; struct TypeFunctionContext @@ -43,7 +64,6 @@ struct TypeFunctionContext const Constraint* constraint; std::optional userFuncName; // Name of the user-defined type function; only available for UDTFs - std::optional userFuncBody; // Body of the user-defined type function; only available for UDTFs TypeFunctionContext(NotNull cs, NotNull scope, NotNull constraint); diff --git a/Analysis/include/Luau/TypePath.h b/Analysis/include/Luau/TypePath.h index 50c75da4c..2af5185d6 100644 --- a/Analysis/include/Luau/TypePath.h +++ b/Analysis/include/Luau/TypePath.h @@ -51,6 +51,8 @@ struct Index /// Represents fields of a type or pack that contain a type. enum class TypeField { + /// The table of a metatable type. + Table, /// The metatable of a type. This could be a metatable type, a primitive /// type, a class type, or perhaps even a string singleton type. Metatable, diff --git a/Analysis/src/AstQuery.cpp b/Analysis/src/AstQuery.cpp index c84703735..6b48b16ea 100644 --- a/Analysis/src/AstQuery.cpp +++ b/Analysis/src/AstQuery.cpp @@ -13,6 +13,8 @@ LUAU_FASTFLAG(LuauSolverV2) +LUAU_FASTFLAGVARIABLE(LuauDocumentationAtPosition, false) + namespace Luau { @@ -509,6 +511,38 @@ static std::optional checkOverloadedDocumentationSymbol( return documentationSymbol; } +static std::optional 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(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 getDocumentationSymbolAtPosition(const SourceModule& source, const Module& module, Position position) { std::vector ancestry = findAstAncestryOfPosition(source, position); @@ -541,15 +575,50 @@ std::optional getDocumentationSymbolAtPosition(const Source } else if (const ClassType* ctv = get(parentTy)) { - if (auto propIt = ctv->props.find(indexName->index.value); propIt != ctv->props.end()) + if (FFlag::LuauDocumentationAtPosition) { - if (FFlag::LuauSolverV2) + while (ctv) { - if (auto ty = propIt->second.readTy) - return checkOverloadedDocumentationSymbol(module, *ty, parentExpr, propIt->second.documentationSymbol); + if (auto propIt = ctv->props.find(indexName->index.value); propIt != ctv->props.end()) + { + if (FFlag::LuauSolverV2) + { + if (auto ty = propIt->second.readTy) + return checkOverloadedDocumentationSymbol(module, *ty, parentExpr, propIt->second.documentationSymbol); + } + else + return checkOverloadedDocumentationSymbol( + module, propIt->second.type(), parentExpr, propIt->second.documentationSymbol + ); + } + ctv = ctv->parent ? Luau::get(*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(parentTy); ptv && ptv->metatable) + { + if (auto mtable = get(*ptv->metatable)) + { + if (std::optional docSymbol = getMetatableDocumentation(module, parentExpr, mtable, indexName->index)) + return docSymbol; } - else - return checkOverloadedDocumentationSymbol(module, propIt->second.type(), parentExpr, propIt->second.documentationSymbol); } } } diff --git a/Analysis/src/Autocomplete.cpp b/Analysis/src/Autocomplete.cpp index 0cb14879a..521c7948c 100644 --- a/Analysis/src/Autocomplete.cpp +++ b/Analysis/src/Autocomplete.cpp @@ -149,7 +149,10 @@ static bool checkTypeMatch(TypeId subTy, TypeId superTy, NotNull scope, T if (FFlag::LuauSolverV2) { - TypeFunctionRuntime typeFunctionRuntime; // TODO: maybe subtyping checks should not invoke user-defined type function runtime + TypeCheckLimits limits; + TypeFunctionRuntime typeFunctionRuntime{ + NotNull{&iceReporter}, NotNull{&limits} + }; // TODO: maybe subtyping checks should not invoke user-defined type function runtime if (FFlag::LuauAutocompleteNewSolverLimit) { diff --git a/Analysis/src/ConstraintGenerator.cpp b/Analysis/src/ConstraintGenerator.cpp index 56a6795aa..efa023bf1 100644 --- a/Analysis/src/ConstraintGenerator.cpp +++ b/Analysis/src/ConstraintGenerator.cpp @@ -29,6 +29,7 @@ LUAU_FASTINT(LuauCheckRecursionLimit); LUAU_FASTFLAG(DebugLuauLogSolverToJson); LUAU_FASTFLAG(DebugLuauMagicTypes); +LUAU_DYNAMIC_FASTINT(LuauTypeSolverRelease); namespace Luau { @@ -191,6 +192,7 @@ bool hasFreeType(TypeId ty) ConstraintGenerator::ConstraintGenerator( ModulePtr module, NotNull normalizer, + NotNull typeFunctionRuntime, NotNull moduleResolver, NotNull builtinTypes, NotNull ice, @@ -206,6 +208,7 @@ ConstraintGenerator::ConstraintGenerator( , rootScope(nullptr) , dfg(dfg) , normalizer(normalizer) + , typeFunctionRuntime(typeFunctionRuntime) , moduleResolver(moduleResolver) , ice(ice) , globalScope(globalScope) @@ -237,7 +240,8 @@ void ConstraintGenerator::visitModuleRoot(AstStatBlock* block) Checkpoint start = checkpoint(this); - ControlFlow cf = visitBlockWithoutChildScope(scope, block); + ControlFlow cf = + DFInt::LuauTypeSolverRelease >= 646 ? visitBlockWithoutChildScope(scope, block) : visitBlockWithoutChildScope_DEPRECATED(scope, block); if (cf == ControlFlow::None) addConstraint(scope, block->location, PackSubtypeConstraint{builtinTypes->emptyTypePack, rootScope->returnType}); @@ -643,6 +647,109 @@ void ConstraintGenerator::applyRefinements(const ScopePtr& scope, Location locat addConstraint(scope, location, c); } +void ConstraintGenerator::checkAliases(const ScopePtr& scope, AstStatBlock* block) +{ + std::unordered_map 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()) + { + 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()) + { + // 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 typeParams; + typeParams.reserve(function->body->args.size); + + std::vector 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 error = typeFunctionRuntime->registerFunction(function)) + reportError(function->location, GenericError{*error}); + + TypeId typeFunctionTy = arena->addType(TypeFunctionInstanceType{ + NotNull{&builtinTypeFunctions().userFunc}, + std::move(typeParams), + {}, + function->name, + }); + + TypeFun typeFunction{std::move(quantifiedTypeParams), typeFunctionTy}; + + // Set type bindings and definition locations for this user-defined type function + scope->privateTypeBindings[function->name.value] = std::move(typeFunction); + aliasDefinitionLocations[function->name.value] = function->location; + } + } +} + ControlFlow ConstraintGenerator::visitBlockWithoutChildScope(const ScopePtr& scope, AstStatBlock* block) { RecursionCounter counter{&recursionCount}; @@ -653,6 +760,29 @@ ControlFlow ConstraintGenerator::visitBlockWithoutChildScope(const ScopePtr& sco return ControlFlow::None; } + checkAliases(scope, block); + + std::optional 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 aliasDefinitionLocations; // In order to enable mutually-recursive type aliases, we need to @@ -709,6 +839,12 @@ ControlFlow ConstraintGenerator::visitBlockWithoutChildScope(const ScopePtr& sco continue; } + if (scope->parent != globalScope) + { + reportError(function->location, GenericError{"Local user-defined functions are not supported yet"}); + continue; + } + ScopePtr defnScope = childScope(function, scope); // Create TypeFunctionInstanceType @@ -729,12 +865,14 @@ ControlFlow ConstraintGenerator::visitBlockWithoutChildScope(const ScopePtr& sco quantifiedTypeParams.push_back(genericTy); } + if (std::optional error = typeFunctionRuntime->registerFunction(function)) + reportError(function->location, GenericError{*error}); + TypeId typeFunctionTy = arena->addType(TypeFunctionInstanceType{ NotNull{&builtinTypeFunctions().userFunc}, std::move(typeParams), {}, function->name, - function->body, }); TypeFun typeFunction{std::move(quantifiedTypeParams), typeFunctionTy}; @@ -1091,7 +1229,10 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatRepeat* rep { ScopePtr repeatScope = childScope(repeat, scope); - visitBlockWithoutChildScope(repeatScope, repeat->body); + if (DFInt::LuauTypeSolverRelease >= 646) + visitBlockWithoutChildScope(repeatScope, repeat->body); + else + visitBlockWithoutChildScope_DEPRECATED(repeatScope, repeat->body); check(repeatScope, repeat->condition); @@ -1265,7 +1406,8 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatBlock* bloc { ScopePtr innerScope = childScope(block, scope); - ControlFlow flow = visitBlockWithoutChildScope(innerScope, block); + ControlFlow flow = DFInt::LuauTypeSolverRelease >= 646 ? visitBlockWithoutChildScope(innerScope, block) + : visitBlockWithoutChildScope_DEPRECATED(innerScope, block); // An AstStatBlock has linear control flow, i.e. one entry and one exit, so we can inherit // all the changes to the environment occurred by the statements in that block. @@ -1456,7 +1598,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeFunctio TypeFun typeFunction = bindingIt->second; // Adding typeAliasExpansionConstraint on user-defined type function for the constraint solver - if (auto typeFunctionTy = get(typeFunction.type)) + if (auto typeFunctionTy = get(DFInt::LuauTypeSolverRelease >= 646 ? follow(typeFunction.type) : typeFunction.type)) { TypeId expansionTy = arena->addType(PendingExpansionType{{}, function->name, typeFunctionTy->typeArguments, typeFunctionTy->packArguments}); addConstraint(scope, function->location, TypeAliasExpansionConstraint{/* target */ expansionTy}); @@ -2511,7 +2653,7 @@ std::tuple ConstraintGenerator::checkBinary( TypeId ty = follow(typeFun->type); // We're only interested in the root class of any classes. - if (auto ctv = get(ty); ctv && ctv->parent == builtinTypes->classType) + if (auto ctv = get(ty); ctv && (ctv->parent == builtinTypes->classType || hasTag(ty, kTypeofRootTag))) discriminantTy = ty; } @@ -2944,7 +3086,8 @@ ConstraintGenerator::FunctionSignature ConstraintGenerator::checkFunctionSignatu void ConstraintGenerator::checkFunctionBody(const ScopePtr& scope, AstExprFunction* fn) { // If it is possible for execution to reach the end of the function, the return type must be compatible with () - ControlFlow cf = visitBlockWithoutChildScope(scope, fn->body); + ControlFlow cf = + DFInt::LuauTypeSolverRelease >= 646 ? visitBlockWithoutChildScope(scope, fn->body) : visitBlockWithoutChildScope_DEPRECATED(scope, fn->body); if (cf == ControlFlow::None) addConstraint(scope, fn->location, PackSubtypeConstraint{builtinTypes->emptyTypePack, scope->returnType}); } diff --git a/Analysis/src/ConstraintSolver.cpp b/Analysis/src/ConstraintSolver.cpp index 7db74cfbd..6c4d78a01 100644 --- a/Analysis/src/ConstraintSolver.cpp +++ b/Analysis/src/ConstraintSolver.cpp @@ -915,9 +915,19 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul auto bindResult = [this, &c, constraint](TypeId result) { - LUAU_ASSERT(get(c.target)); - shiftReferences(c.target, result); - bind(constraint, c.target, result); + if (DFInt::LuauTypeSolverRelease >= 646) + { + auto cTarget = follow(c.target); + LUAU_ASSERT(get(cTarget)); + shiftReferences(cTarget, result); + bind(constraint, cTarget, result); + } + else + { + LUAU_ASSERT(get(c.target)); + shiftReferences(c.target, result); + bind(constraint, c.target, result); + } }; std::optional tf = (petv->prefix) ? constraint->scope->lookupImportedType(petv->prefix->value, petv->name.value) @@ -945,7 +955,7 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul // Due to how pending expansion types and TypeFun's are created // If this check passes, we have created a cyclic / corecursive type alias // of size 0 - TypeId lhs = c.target; + TypeId lhs = DFInt::LuauTypeSolverRelease >= 646 ? follow(c.target) : c.target; TypeId rhs = tf->type; if (occursCheck(lhs, rhs)) { diff --git a/Analysis/src/Error.cpp b/Analysis/src/Error.cpp index c91ce00d5..66b61d6bc 100644 --- a/Analysis/src/Error.cpp +++ b/Analysis/src/Error.cpp @@ -18,8 +18,6 @@ LUAU_FASTINTVARIABLE(LuauIndentTypeMismatchMaxTypeLength, 10) -LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauImproveNonFunctionCallError, false) - static std::string wrongNumberOfArgsString( size_t expectedCount, std::optional maximumCount, @@ -408,35 +406,30 @@ struct ErrorConverter std::string operator()(const Luau::CannotCallNonFunction& e) const { - if (DFFlag::LuauImproveNonFunctionCallError) + if (auto unionTy = get(follow(e.ty))) { - if (auto unionTy = get(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(option) || findCallMetamethod(option)) { - option = follow(option); - - if (get(option) || findCallMetamethod(option)) - { - err += "\n | " + toString(option); - continue; - } - - // early-exit if we find something that isn't callable in the union. - return "Cannot call a value of type " + toString(option) + " in union:\n " + toString(e.ty); + err += "\n | " + toString(option); + continue; } - err += "\nWe are unable to determine the appropriate result type for such a call."; - - return err; + // early-exit if we find something that isn't callable in the union. + return "Cannot call a value of type " + toString(option) + " in union:\n " + toString(e.ty); } - return "Cannot call a value of type " + toString(e.ty); + err += "\nWe are unable to determine the appropriate result type for such a call."; + + return err; } - return "Cannot call non-function " + toString(e.ty); + return "Cannot call a value of type " + toString(e.ty); } std::string operator()(const Luau::ExtraInformation& e) const { diff --git a/Analysis/src/Frontend.cpp b/Analysis/src/Frontend.cpp index ca6277284..edcabc4af 100644 --- a/Analysis/src/Frontend.cpp +++ b/Analysis/src/Frontend.cpp @@ -45,6 +45,7 @@ LUAU_FASTFLAGVARIABLE(DebugLuauForbidInternalTypes, false) LUAU_FASTFLAGVARIABLE(DebugLuauForceStrictMode, false) LUAU_FASTFLAGVARIABLE(DebugLuauForceNonStrictMode, false) LUAU_FASTFLAGVARIABLE(LuauSourceModuleUpdatedWithSelectedMode, false) +LUAU_FASTFLAGVARIABLE(LuauUserDefinedTypeFunctionNoEvaluation, false) LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauRunCustomModuleChecks, false) LUAU_FASTFLAG(StudioReportLuauAny2) @@ -205,72 +206,6 @@ LoadDefinitionFileResult Frontend::loadDefinitionFile( return LoadDefinitionFileResult{true, parseResult, sourceModule, checkedModule}; } -std::vector parsePathExpr(const AstExpr& pathExpr) -{ - const AstExprIndexName* indexName = pathExpr.as(); - if (!indexName) - return {}; - - std::vector segments{indexName->index.value}; - - while (true) - { - if (AstExprIndexName* in = indexName->expr->as()) - { - segments.push_back(in->index.value); - indexName = in; - continue; - } - else if (AstExprGlobal* indexNameAsGlobal = indexName->expr->as()) - { - segments.push_back(indexNameAsGlobal->name.value); - break; - } - else if (AstExprLocal* indexNameAsLocal = indexName->expr->as()) - { - segments.push_back(indexNameAsLocal->local->name.value); - break; - } - else - return {}; - } - - std::reverse(segments.begin(), segments.end()); - return segments; -} - -std::optional pathExprToModuleName(const ModuleName& currentModuleName, const std::vector& segments) -{ - if (segments.empty()) - return std::nullopt; - - std::vector 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 pathExprToModuleName(const ModuleName& currentModuleName, const AstExpr& pathExpr) -{ - std::vector segments = parsePathExpr(pathExpr); - return pathExprToModuleName(currentModuleName, segments); -} - namespace { @@ -1383,11 +1318,15 @@ ModulePtr check( unifierState.counters.iterationLimit = limits.unifierIterationLimit.value_or(FInt::LuauTypeInferIterationLimit); Normalizer normalizer{&result->internalTypes, builtinTypes, NotNull{&unifierState}}; - TypeFunctionRuntime typeFunctionRuntime; + TypeFunctionRuntime typeFunctionRuntime{iceHandler, NotNull{&limits}}; + + if (FFlag::LuauUserDefinedTypeFunctionNoEvaluation) + typeFunctionRuntime.allowEvaluation = sourceModule.parseErrors.empty(); ConstraintGenerator cg{ result, NotNull{&normalizer}, + NotNull{&typeFunctionRuntime}, moduleResolver, builtinTypes, iceHandler, @@ -1463,12 +1402,23 @@ ModulePtr check( switch (mode) { case Mode::Nonstrict: - Luau::checkNonStrict(builtinTypes, iceHandler, NotNull{&unifierState}, NotNull{&dfg}, NotNull{&limits}, sourceModule, result.get()); + Luau::checkNonStrict( + builtinTypes, + NotNull{&typeFunctionRuntime}, + iceHandler, + NotNull{&unifierState}, + NotNull{&dfg}, + NotNull{&limits}, + sourceModule, + result.get() + ); break; case Mode::Definition: // fallthrough intentional case Mode::Strict: - Luau::check(builtinTypes, NotNull{&unifierState}, NotNull{&limits}, logger.get(), sourceModule, result.get()); + Luau::check( + builtinTypes, NotNull{&typeFunctionRuntime}, NotNull{&unifierState}, NotNull{&limits}, logger.get(), sourceModule, result.get() + ); break; case Mode::NoCheck: break; diff --git a/Analysis/src/Generalization.cpp b/Analysis/src/Generalization.cpp index a79814ec6..8c6cf378e 100644 --- a/Analysis/src/Generalization.cpp +++ b/Analysis/src/Generalization.cpp @@ -528,7 +528,12 @@ struct TypeCacher : TypeOnceVisitor DenseHashSet uncacheablePacks{nullptr}; explicit TypeCacher(NotNull> cachedTypes) - : TypeOnceVisitor(/* skipBoundTypes */ true) + // CLI-120975: once we roll out release 646, we _want_ to visit bound + // types to ensure they're marked as uncacheable if the types they are + // bound to are also uncacheable. Hence: if LuauTypeSolverRelease is + // less than 646, skip bound types (the prior behavior). Otherwise, + // do not skip bound types. + : TypeOnceVisitor(/* skipBoundTypes */ DFInt::LuauTypeSolverRelease < 646) , cachedTypes(cachedTypes) { } @@ -565,9 +570,33 @@ struct TypeCacher : TypeOnceVisitor bool visit(TypeId ty) override { - if (isUncacheable(ty) || isCached(ty)) + if (DFInt::LuauTypeSolverRelease >= 646) + { + // NOTE: `TypeCacher` should explicitly visit _all_ types and type packs, + // otherwise it's prone to marking types that cannot be cached as + // cacheable. + LUAU_ASSERT(false); + LUAU_UNREACHABLE(); + } + else + { + return true; + } + } + + bool visit(TypeId ty, const BoundType& btv) override + { + if (DFInt::LuauTypeSolverRelease >= 646) + { + traverse(btv.boundTo); + if (isUncacheable(btv.boundTo)) + markUncacheable(ty); return false; - return true; + } + else + { + return true; + } } bool visit(TypeId ty, const FreeType& ft) override @@ -592,6 +621,19 @@ struct TypeCacher : TypeOnceVisitor return false; } + bool visit(TypeId ty, const ErrorType&) override + { + if (DFInt::LuauTypeSolverRelease >= 646) + { + cache(ty); + return false; + } + else + { + return true; + } + } + bool visit(TypeId ty, const PrimitiveType&) override { cache(ty); @@ -729,6 +771,24 @@ struct TypeCacher : TypeOnceVisitor return false; } + bool visit(TypeId ty, const MetatableType& mtv) override + { + if (DFInt::LuauTypeSolverRelease >= 646) + { + traverse(mtv.table); + traverse(mtv.metatable); + if (isUncacheable(mtv.table) || isUncacheable(mtv.metatable)) + markUncacheable(ty); + else + cache(ty); + return false; + } + else + { + return true; + } + } + bool visit(TypeId ty, const ClassType&) override { cache(ty); @@ -843,12 +903,38 @@ struct TypeCacher : TypeOnceVisitor return false; } + bool visit(TypePackId tp) override + { + if (DFInt::LuauTypeSolverRelease >= 646) + { + // NOTE: `TypeCacher` should explicitly visit _all_ types and type packs, + // otherwise it's prone to marking types that cannot be cached as + // cacheable, which will segfault down the line. + LUAU_ASSERT(false); + LUAU_UNREACHABLE(); + } + else + { + return true; + } + } + bool visit(TypePackId tp, const FreeTypePack&) override { markUncacheable(tp); return false; } + bool visit(TypePackId tp, const GenericTypePack& gtp) override + { + return true; + } + + bool visit(TypePackId tp, const Unifiable::Error& etp) override + { + return true; + } + bool visit(TypePackId tp, const VariadicTypePack& vtp) override { if (isUncacheable(tp)) @@ -884,6 +970,27 @@ struct TypeCacher : TypeOnceVisitor return true; } + bool visit(TypePackId tp, const TypePack& typ) override + { + if (DFInt::LuauTypeSolverRelease >= 646) + { + bool uncacheable = false; + for (TypeId ty : typ.head) + { + traverse(ty); + uncacheable |= isUncacheable(ty); + } + if (typ.tail) + { + traverse(*typ.tail); + uncacheable |= isUncacheable(*typ.tail); + } + if (uncacheable) + markUncacheable(tp); + return false; + } + return true; + } }; std::optional generalize( diff --git a/Analysis/src/Module.cpp b/Analysis/src/Module.cpp index 564a3c353..0c5b361cf 100644 --- a/Analysis/src/Module.cpp +++ b/Analysis/src/Module.cpp @@ -146,7 +146,20 @@ struct ClonePublicInterface : Substitution { if (auto freety = getMutable(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(result)) { @@ -159,7 +172,35 @@ struct ClonePublicInterface : Substitution TypePackId clean(TypePackId tp) override { - return clone(tp); + if (FFlag::LuauSolverV2 && DFInt::LuauTypeSolverRelease >= 645) + { + auto clonedTp = clone(tp); + if (auto ftp = getMutable(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(clonedTp)) + gtp->scope = nullptr; + return clonedTp; + } + else + { + return clone(tp); + } } TypeId cloneType(TypeId ty) diff --git a/Analysis/src/NonStrictTypeChecker.cpp b/Analysis/src/NonStrictTypeChecker.cpp index 2131887a9..7d5859ce1 100644 --- a/Analysis/src/NonStrictTypeChecker.cpp +++ b/Analysis/src/NonStrictTypeChecker.cpp @@ -154,13 +154,12 @@ struct NonStrictContext struct NonStrictTypeChecker { - NotNull builtinTypes; + NotNull typeFunctionRuntime; const NotNull ice; NotNull arena; Module* module; Normalizer normalizer; - TypeFunctionRuntime typeFunctionRuntime; Subtyping subtyping; NotNull dfg; DenseHashSet noTypeFunctionErrors{nullptr}; @@ -172,6 +171,7 @@ struct NonStrictTypeChecker NonStrictTypeChecker( NotNull arena, NotNull builtinTypes, + NotNull typeFunctionRuntime, const NotNull ice, NotNull unifierState, NotNull dfg, @@ -179,11 +179,12 @@ struct NonStrictTypeChecker Module* module ) : builtinTypes(builtinTypes) + , typeFunctionRuntime(typeFunctionRuntime) , ice(ice) , arena(arena) , module(module) , normalizer{arena, builtinTypes, unifierState, /* cache inhabitance */ true} - , subtyping{builtinTypes, arena, NotNull(&normalizer), NotNull(&typeFunctionRuntime), ice} + , subtyping{builtinTypes, arena, NotNull(&normalizer), typeFunctionRuntime, ice} , dfg(dfg) , limits(limits) { @@ -228,14 +229,13 @@ struct NonStrictTypeChecker if (noTypeFunctionErrors.find(instance)) return instance; - ErrorVec errors = - reduceTypeFunctions( - instance, - location, - TypeFunctionContext{arena, builtinTypes, stack.back(), NotNull{&normalizer}, NotNull{&typeFunctionRuntime}, ice, limits}, - true - ) - .errors; + ErrorVec errors = reduceTypeFunctions( + instance, + location, + TypeFunctionContext{arena, builtinTypes, stack.back(), NotNull{&normalizer}, typeFunctionRuntime, ice, limits}, + true + ) + .errors; if (errors.empty()) noTypeFunctionErrors.insert(instance); @@ -760,6 +760,7 @@ struct NonStrictTypeChecker void checkNonStrict( NotNull builtinTypes, + NotNull typeFunctionRuntime, NotNull ice, NotNull unifierState, NotNull dfg, @@ -770,7 +771,7 @@ void checkNonStrict( { LUAU_TIMETRACE_SCOPE("checkNonStrict", "Typechecking"); - NonStrictTypeChecker typeChecker{NotNull{&module->internalTypes}, builtinTypes, ice, unifierState, dfg, limits, module}; + NonStrictTypeChecker typeChecker{NotNull{&module->internalTypes}, builtinTypes, typeFunctionRuntime, ice, unifierState, dfg, limits, module}; typeChecker.visit(sourceModule.root); unfreeze(module->interfaceTypes); copyErrors(module->errors, module->interfaceTypes, builtinTypes); diff --git a/Analysis/src/Normalize.cpp b/Analysis/src/Normalize.cpp index 7ca57e617..01f896d53 100644 --- a/Analysis/src/Normalize.cpp +++ b/Analysis/src/Normalize.cpp @@ -16,8 +16,6 @@ #include "Luau/Unifier.h" LUAU_FASTFLAGVARIABLE(DebugLuauCheckNormalizeInvariant, false) -LUAU_FASTFLAGVARIABLE(LuauNormalizeAwayUninhabitableTables, false) -LUAU_FASTFLAGVARIABLE(LuauNormalizeNotUnknownIntersection, false); LUAU_FASTFLAGVARIABLE(LuauFixReduceStackPressure, false); LUAU_FASTFLAGVARIABLE(LuauFixCyclicTablesBlowingStack, false); @@ -40,12 +38,6 @@ static bool fixCyclicTablesBlowingStack() namespace Luau { -// helper to make `FFlag::LuauNormalizeAwayUninhabitableTables` not explicitly required when DCR is enabled. -static bool normalizeAwayUninhabitableTables() -{ - return FFlag::LuauNormalizeAwayUninhabitableTables || FFlag::LuauSolverV2; -} - static bool shouldEarlyExit(NormalizationResult res) { // if res is hit limits, return control flow @@ -1621,7 +1613,7 @@ void Normalizer::unionTablesWithTable(TypeIds& heres, TypeId there) // TODO: remove unions of tables where possible // we can always skip `never` - if (normalizeAwayUninhabitableTables() && get(there)) + if (get(there)) return; heres.insert(there); @@ -2619,13 +2611,12 @@ std::optional Normalizer::intersectionOfTables(TypeId here, TypeId there seenSet.erase(*tprop.readTy); } - if (normalizeAwayUninhabitableTables() && NormalizationResult::True != res) + if (NormalizationResult::True != res) return {builtinTypes->neverType}; } else { - if (normalizeAwayUninhabitableTables() && - NormalizationResult::False == isIntersectionInhabited(*hprop.readTy, *tprop.readTy)) + if (NormalizationResult::False == isIntersectionInhabited(*hprop.readTy, *tprop.readTy)) return {builtinTypes->neverType}; } @@ -3258,7 +3249,7 @@ NormalizationResult Normalizer::intersectNormalWithTy(NormalizedType& here, Type // this is a noop since an intersection with `unknown` is trivial. return NormalizationResult::True; } - else if ((FFlag::LuauNormalizeNotUnknownIntersection || FFlag::LuauSolverV2) && get(t)) + else if (get(t)) { // if we're intersecting with `~unknown`, this is equivalent to intersecting with `never` // this means we should clear the type entirely. @@ -3434,7 +3425,10 @@ bool isSubtype(TypeId subTy, TypeId superTy, NotNull scope, NotNull scope, N UnifierSharedState sharedState{&ice}; TypeArena arena; Normalizer normalizer{&arena, builtinTypes, NotNull{&sharedState}}; - TypeFunctionRuntime typeFunctionRuntime; // TODO: maybe subtyping checks should not invoke user-defined type function runtime + TypeCheckLimits limits; + TypeFunctionRuntime typeFunctionRuntime{ + NotNull{&ice}, NotNull{&limits} + }; // TODO: maybe subtyping checks should not invoke user-defined type function runtime // Subtyping under DCR is not implemented using unification! if (FFlag::LuauSolverV2) diff --git a/Analysis/src/Substitution.cpp b/Analysis/src/Substitution.cpp index 526d8212b..634f52416 100644 --- a/Analysis/src/Substitution.cpp +++ b/Analysis/src/Substitution.cpp @@ -127,7 +127,7 @@ static TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log, bool a return dest.addType(NegationType{a.ty}); else if constexpr (std::is_same_v) { - TypeFunctionInstanceType clone{a.function, a.typeArguments, a.packArguments, a.userFuncName, a.userFuncBody}; + TypeFunctionInstanceType clone{a.function, a.typeArguments, a.packArguments, a.userFuncName}; return dest.addType(std::move(clone)); } else diff --git a/Analysis/src/Subtyping.cpp b/Analysis/src/Subtyping.cpp index f8347c720..f3571b7a1 100644 --- a/Analysis/src/Subtyping.cpp +++ b/Analysis/src/Subtyping.cpp @@ -1455,8 +1455,17 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Tabl SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const MetatableType* subMt, const MetatableType* superMt, NotNull scope) { - return isCovariantWith(env, subMt->table, superMt->table, scope) - .andAlso(isCovariantWith(env, subMt->metatable, superMt->metatable, scope).withBothComponent(TypePath::TypeField::Metatable)); + if (DFInt::LuauTypeSolverRelease >= 646) + { + return isCovariantWith(env, subMt->table, superMt->table, scope) + .withBothComponent(TypePath::TypeField::Table) + .andAlso(isCovariantWith(env, subMt->metatable, superMt->metatable, scope).withBothComponent(TypePath::TypeField::Metatable)); + } + else + { + return isCovariantWith(env, subMt->table, superMt->table, scope) + .andAlso(isCovariantWith(env, subMt->metatable, superMt->metatable, scope).withBothComponent(TypePath::TypeField::Metatable)); + } } SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const MetatableType* subMt, const TableType* superTable, NotNull scope) diff --git a/Analysis/src/TypeChecker2.cpp b/Analysis/src/TypeChecker2.cpp index 3dc708a2d..db6188159 100644 --- a/Analysis/src/TypeChecker2.cpp +++ b/Analysis/src/TypeChecker2.cpp @@ -31,7 +31,7 @@ #include LUAU_FASTFLAG(DebugLuauMagicTypes) -LUAU_FASTFLAG(LuauUserDefinedTypeFunctions) +LUAU_FASTFLAG(LuauUserDefinedTypeFunctions2) LUAU_DYNAMIC_FASTINT(LuauTypeSolverRelease) namespace Luau @@ -268,6 +268,7 @@ struct InternalTypeFunctionFinder : TypeOnceVisitor void check( NotNull builtinTypes, + NotNull typeFunctionRuntime, NotNull unifierState, NotNull limits, DcrLogger* logger, @@ -277,7 +278,7 @@ void check( { LUAU_TIMETRACE_SCOPE("check", "Typechecking"); - TypeChecker2 typeChecker{builtinTypes, unifierState, limits, logger, &sourceModule, module}; + TypeChecker2 typeChecker{builtinTypes, typeFunctionRuntime, unifierState, limits, logger, &sourceModule, module}; typeChecker.visit(sourceModule.root); @@ -294,6 +295,7 @@ void check( TypeChecker2::TypeChecker2( NotNull builtinTypes, + NotNull typeFunctionRuntime, NotNull unifierState, NotNull limits, DcrLogger* logger, @@ -301,13 +303,14 @@ TypeChecker2::TypeChecker2( Module* module ) : builtinTypes(builtinTypes) + , typeFunctionRuntime(typeFunctionRuntime) , logger(logger) , limits(limits) , ice(unifierState->iceHandler) , sourceModule(sourceModule) , module(module) , normalizer{&module->internalTypes, builtinTypes, unifierState, /* cacheInhabitance */ true} - , _subtyping{builtinTypes, NotNull{&module->internalTypes}, NotNull{&normalizer}, NotNull{&typeFunctionRuntime}, NotNull{unifierState->iceHandler}} + , _subtyping{builtinTypes, NotNull{&module->internalTypes}, NotNull{&normalizer}, typeFunctionRuntime, NotNull{unifierState->iceHandler}} , subtyping(&_subtyping) { } @@ -489,9 +492,7 @@ TypeId TypeChecker2::checkForTypeFunctionInhabitance(TypeId instance, Location l reduceTypeFunctions( instance, location, - TypeFunctionContext{ - NotNull{&module->internalTypes}, builtinTypes, stack.back(), NotNull{&normalizer}, NotNull{&typeFunctionRuntime}, ice, limits - }, + TypeFunctionContext{NotNull{&module->internalTypes}, builtinTypes, stack.back(), NotNull{&normalizer}, typeFunctionRuntime, ice, limits}, true ) .errors; @@ -1198,7 +1199,7 @@ void TypeChecker2::visit(AstStatTypeAlias* stat) void TypeChecker2::visit(AstStatTypeFunction* stat) { // TODO: add type checking for user-defined type functions - if (!FFlag::LuauUserDefinedTypeFunctions) + if (!FFlag::LuauUserDefinedTypeFunctions2) reportError(TypeError{stat->location, GenericError{"This syntax is not supported"}}); } @@ -1450,7 +1451,7 @@ void TypeChecker2::visitCall(AstExprCall* call) builtinTypes, NotNull{&module->internalTypes}, NotNull{&normalizer}, - NotNull{&typeFunctionRuntime}, + typeFunctionRuntime, NotNull{stack.back()}, ice, limits, diff --git a/Analysis/src/TypeFunction.cpp b/Analysis/src/TypeFunction.cpp index 6d928faaa..f2a7fc8f5 100644 --- a/Analysis/src/TypeFunction.cpp +++ b/Analysis/src/TypeFunction.cpp @@ -46,7 +46,8 @@ LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFamilyApplicationCartesianProductLimit, 5'0 LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFamilyUseGuesserDepth, -1); LUAU_FASTFLAGVARIABLE(DebugLuauLogTypeFamilies, false) -LUAU_FASTFLAGVARIABLE(LuauUserDefinedTypeFunctions, false) +LUAU_FASTFLAGVARIABLE(LuauUserDefinedTypeFunctions2, false) +LUAU_FASTFLAG(LuauUserDefinedTypeFunctionNoEvaluation) LUAU_DYNAMIC_FASTINT(LuauTypeSolverRelease) @@ -375,7 +376,6 @@ struct TypeFunctionReducer return; ctx.userFuncName = tfit->userFuncName; - ctx.userFuncBody = tfit->userFuncBody; TypeFunctionReductionResult result = tfit->function->reducer(subject, tfit->typeArguments, tfit->packArguments, NotNull{&ctx}); handleTypeFunctionReduction(subject, result); @@ -416,6 +416,20 @@ struct TypeFunctionReducer } }; +struct LuauTempThreadPopper +{ + explicit LuauTempThreadPopper(lua_State* L) + : L(L) + { + } + ~LuauTempThreadPopper() + { + lua_pop(L, 1); + } + + lua_State* L = nullptr; +}; + static FunctionGraphReductionResult reduceFunctionsInternal( VecDeque queuedTys, VecDeque queuedTps, @@ -586,8 +600,6 @@ static std::optional> tryDistributeTypeFunct return std::nullopt; } -using StateRef = std::unique_ptr; - TypeFunctionReductionResult userDefinedTypeFunction( TypeId instance, const std::vector& typeParams, @@ -595,12 +607,19 @@ TypeFunctionReductionResult userDefinedTypeFunction( NotNull ctx ) { - if (!ctx->userFuncName || !ctx->userFuncBody) + if (!ctx->userFuncName) { ctx->ice->ice("all user-defined type functions must have an associated function definition"); return {std::nullopt, true, {}, {}}; } + if (FFlag::LuauUserDefinedTypeFunctionNoEvaluation) + { + // If type functions cannot be evaluated because of errors in the code, we do not generate any additional ones + if (!ctx->typeFunctionRuntime->allowEvaluation) + return {ctx->builtins->errorRecoveryType(), false, {}, {}}; + } + for (auto typeParam : typeParams) { TypeId ty = follow(typeParam); @@ -611,62 +630,18 @@ TypeFunctionReductionResult userDefinedTypeFunction( } AstName name = *ctx->userFuncName; - AstExprFunction* function = *ctx->userFuncBody; - - // Construct ParseResult containing the type function - Allocator allocator; - AstNameTable names(allocator); - - AstExprGlobal globalName{Location{}, name}; - AstStatFunction typeFunction{Location{}, &globalName, function}; - AstStat* stmtArray[] = {&typeFunction}; - AstArray stmts{stmtArray, 1}; - AstStatBlock exec{Location{}, stmts}; - ParseResult parseResult{&exec, 1}; - - BytecodeBuilder builder; - try - { - compileOrThrow(builder, parseResult, names); - } - catch (CompileError& e) - { - std::string errMsg = format("'%s' type function failed to compile with error message: %s", name.value, e.what()); - return {std::nullopt, true, {}, {}, errMsg}; - } - - std::string bytecode = builder.getBytecode(); - // Initialize Lua state - StateRef globalState(lua_newstate(typeFunctionAlloc, nullptr), lua_close); - lua_State* L = globalState.get(); - - lua_setthreaddata(L, ctx.get()); - - setTypeFunctionEnvironment(L); + lua_State* global = ctx->typeFunctionRuntime->state.get(); - // Register type userdata - registerTypeUserData(L); + if (global == nullptr) + return {std::nullopt, true, {}, {}, format("'%s' type function: cannot be evaluated in this context", name.value)}; - luaL_sandbox(L); - luaL_sandboxthread(L); + // Separate sandboxed thread for individual execution and private globals + lua_State* L = lua_newthread(global); + LuauTempThreadPopper popper(global); - // Load bytecode into Luau state - if (auto error = checkResultForError(L, name.value, luau_load(L, name.value, bytecode.data(), bytecode.size(), 0))) - return {std::nullopt, true, {}, {}, error}; - - // Execute the loaded chunk to register the function in the global environment - if (auto error = checkResultForError(L, name.value, lua_pcall(L, 0, 0, 0))) - return {std::nullopt, true, {}, {}, error}; - - // Get type function from the global environment - lua_getglobal(L, name.value); - if (!lua_isfunction(L, -1)) - { - std::string errMsg = format("Could not find '%s' type function in the global scope", name.value); - - return {std::nullopt, true, {}, {}, errMsg}; - } + lua_getglobal(global, name.value); + lua_xmove(global, L, 1); // Push serialized arguments onto the stack @@ -690,15 +665,15 @@ TypeFunctionReductionResult userDefinedTypeFunction( // Set up an interrupt handler for type functions to respect type checking limits and LSP cancellation requests. lua_callbacks(L)->interrupt = [](lua_State* L, int gc) { - auto ctx = static_cast(lua_getthreaddata(lua_mainthread(L))); + auto ctx = static_cast(lua_getthreaddata(lua_mainthread(L))); if (ctx->limits->finishTime && TimeTrace::getClock() > *ctx->limits->finishTime) - ctx->solver->throwTimeLimitError(); + throw TimeLimitError(ctx->ice->moduleName); if (ctx->limits->cancellationToken && ctx->limits->cancellationToken->requested()) - ctx->solver->throwUserCancelError(); + throw UserCancelError(ctx->ice->moduleName); }; - if (auto error = checkResultForError(L, name.value, lua_resume(L, nullptr, int(typeParams.size())))) + if (auto error = checkResultForError(L, name.value, lua_pcall(L, int(typeParams.size()), 1, 0))) return {std::nullopt, true, {}, {}, error}; // If the return value is not a type userdata, return with error message @@ -796,7 +771,8 @@ TypeFunctionReductionResult lenTypeFunction( return {ctx->builtins->numberType, false, {}, {}}; // we use the normalized operand here in case there was an intersection or union. - TypeId normalizedOperand = ctx->normalizer->typeFromNormal(*normTy); + TypeId normalizedOperand = + DFInt::LuauTypeSolverRelease >= 646 ? follow(ctx->normalizer->typeFromNormal(*normTy)) : ctx->normalizer->typeFromNormal(*normTy); if (normTy->hasTopTable() || get(normalizedOperand)) return {ctx->builtins->numberType, false, {}, {}}; @@ -947,6 +923,108 @@ TypeFunctionReductionResult unmTypeFunction( return {std::nullopt, true, {}, {}}; } +void dummyStateClose(lua_State*) {} + +TypeFunctionRuntime::TypeFunctionRuntime(NotNull ice, NotNull limits) + : ice(ice) + , limits(limits) + , state(nullptr, dummyStateClose) +{ +} + +TypeFunctionRuntime::~TypeFunctionRuntime() {} + +std::optional 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 exprReturns{&exprFunction, 1}; + AstStatReturn stmtReturn{Location{}, exprReturns}; + AstStat* stmtArray[] = {&stmtReturn}; + AstArray 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 cs, NotNull scope, NotNull constraint) : arena(cs->arena) , builtins(cs->builtinTypes) diff --git a/Analysis/src/TypeFunctionRuntime.cpp b/Analysis/src/TypeFunctionRuntime.cpp index d3a33d077..b0d9285ef 100644 --- a/Analysis/src/TypeFunctionRuntime.cpp +++ b/Analysis/src/TypeFunctionRuntime.cpp @@ -13,8 +13,8 @@ #include #include -// defined in TypeFunctionRuntimeBuilder.cpp -LUAU_DYNAMIC_FASTINT(LuauTypeFunctionSerdeIterationLimit); +LUAU_DYNAMIC_FASTINT(LuauTypeFunctionSerdeIterationLimit) +LUAU_DYNAMIC_FASTINT(LuauTypeSolverRelease) namespace Luau { @@ -63,21 +63,21 @@ std::optional checkResultForError(lua_State* L, const char* typeFun } } -static const TypeFunctionContext* getTypeFunctionContext(lua_State* L) +static TypeFunctionRuntime* getTypeFunctionRuntime(lua_State* L) { - return static_cast(lua_getthreaddata(lua_mainthread(L))); + return static_cast(lua_getthreaddata(lua_mainthread(L))); } TypeFunctionType* allocateTypeFunctionType(lua_State* L, TypeFunctionTypeVariant type) { - auto ctx = getTypeFunctionContext(L); - return ctx->typeFunctionRuntime->typeArena.allocate(std::move(type)); + auto ctx = getTypeFunctionRuntime(L); + return ctx->typeArena.allocate(std::move(type)); } TypeFunctionTypePackVar* allocateTypeFunctionTypePack(lua_State* L, TypeFunctionTypePackVariant type) { - auto ctx = getTypeFunctionContext(L); - return ctx->typeFunctionRuntime->typePackArena.allocate(std::move(type)); + auto ctx = getTypeFunctionRuntime(L); + return ctx->typePackArena.allocate(std::move(type)); } // Pushes a new type userdata onto the stack @@ -678,7 +678,7 @@ static int writeTableProp(lua_State* L) } // Luau: `self:setindexer(key: type, value: type)` -// Sets the indexer of the table +// Sets the indexer of the table, if the key type is `never`, the indexer is removed static int setTableIndexer(lua_State* L) { int argumentCount = lua_gettop(L); @@ -693,8 +693,16 @@ static int setTableIndexer(lua_State* L) TypeFunctionTypeId key = getTypeUserData(L, 2); TypeFunctionTypeId value = getTypeUserData(L, 3); - tftt->indexer = TypeFunctionTableIndexer{key, value}; + if (DFInt::LuauTypeSolverRelease >= 646) + { + if (auto tfnt = get(key)) + { + tftt->indexer = std::nullopt; + return 0; + } + } + tftt->indexer = TypeFunctionTableIndexer{key, value}; return 0; } @@ -1353,7 +1361,7 @@ static int deepCopy(lua_State* L) TypeFunctionTypeId arg = getTypeUserData(L, 1); - TypeFunctionTypeId copy = deepClone(getTypeFunctionContext(L)->typeFunctionRuntime, arg); + TypeFunctionTypeId copy = deepClone(NotNull{getTypeFunctionRuntime(L)}, arg); allocTypeUserData(L, copy->type); return 1; } diff --git a/Analysis/src/TypeInfer.cpp b/Analysis/src/TypeInfer.cpp index 7a7be71d7..b681e15cd 100644 --- a/Analysis/src/TypeInfer.cpp +++ b/Analysis/src/TypeInfer.cpp @@ -32,7 +32,6 @@ LUAU_FASTINTVARIABLE(LuauVisitRecursionLimit, 500) LUAU_FASTFLAG(LuauKnowsTheDataModel3) LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false) LUAU_FASTFLAG(LuauInstantiateInSubtyping) -LUAU_FASTFLAGVARIABLE(LuauRemoveBadRelationalOperatorWarning, false) LUAU_FASTFLAGVARIABLE(LuauAcceptIndexingTableUnionsIntersections, false) namespace Luau @@ -2794,35 +2793,20 @@ TypeId TypeChecker::checkRelationalOperation( { reportErrors(state.errors); - if (FFlag::LuauRemoveBadRelationalOperatorWarning) + // The original version of this check also produced this error when we had a union type. + // However, the old solver does not readily have the ability to discern if the union is comparable. + // This is the case when the lhs is e.g. a union of singletons and the rhs is the combined type. + // The new solver has much more powerful logic for resolving relational operators, but for now, + // we need to be conservative in the old solver to deliver a reasonable developer experience. + if (!isEquality && state.errors.empty() && isBoolean(leftType)) { - // The original version of this check also produced this error when we had a union type. - // However, the old solver does not readily have the ability to discern if the union is comparable. - // This is the case when the lhs is e.g. a union of singletons and the rhs is the combined type. - // The new solver has much more powerful logic for resolving relational operators, but for now, - // we need to be conservative in the old solver to deliver a reasonable developer experience. - if (!isEquality && state.errors.empty() && isBoolean(leftType)) - { - reportError( - expr.location, - GenericError{ - format("Type '%s' cannot be compared with relational operator %s", toString(leftType).c_str(), toString(expr.op).c_str()) - } - ); + reportError( + expr.location, + GenericError{ + format("Type '%s' cannot be compared with relational operator %s", toString(leftType).c_str(), toString(expr.op).c_str()) } - } - else - { - if (!isEquality && state.errors.empty() && (get(leftType) || isBoolean(leftType))) - { - reportError( - expr.location, - GenericError{ - format("Type '%s' cannot be compared with relational operator %s", toString(leftType).c_str(), toString(expr.op).c_str()) - } ); } - } return booleanType; } @@ -6408,7 +6392,7 @@ void TypeChecker::resolve(const TypeGuardPredicate& typeguardP, RefinementMap& r } // We're only interested in the root class of any classes. - if (auto ctv = get(type); !ctv || ctv->parent != builtinTypes->classType) + if (auto ctv = get(type); !ctv || (ctv->parent != builtinTypes->classType && !hasTag(type, kTypeofRootTag))) return addRefinement(refis, typeguardP.lvalue, errorRecoveryType(scope)); // This probably hints at breaking out type filtering functions from the predicate solver so that typeof is not tightly coupled with IsA. diff --git a/Analysis/src/TypePath.cpp b/Analysis/src/TypePath.cpp index d2113ee3e..855ac3034 100644 --- a/Analysis/src/TypePath.cpp +++ b/Analysis/src/TypePath.cpp @@ -415,6 +415,14 @@ struct TraversalState switch (field) { + case TypePath::TypeField::Table: + if (auto mt = get(current)) + { + updateCurrent(mt->table); + return true; + } + + return false; case TypePath::TypeField::Metatable: if (auto currentType = get(current)) { @@ -561,6 +569,9 @@ std::string toString(const TypePath::Path& path, bool prefixDot) switch (c) { + case TypePath::TypeField::Table: + result << "table"; + break; case TypePath::TypeField::Metatable: result << "metatable"; break; diff --git a/Analysis/src/Unifier.cpp b/Analysis/src/Unifier.cpp index fa7ff8762..25f05a6f6 100644 --- a/Analysis/src/Unifier.cpp +++ b/Analysis/src/Unifier.cpp @@ -21,7 +21,6 @@ LUAU_FASTFLAGVARIABLE(LuauInstantiateInSubtyping, false) LUAU_FASTFLAGVARIABLE(LuauTransitiveSubtyping, false) LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAGVARIABLE(LuauFixIndexerSubtypingOrdering, false) -LUAU_FASTFLAGVARIABLE(LuauUnifierShouldNotCopyError, false) LUAU_FASTFLAGVARIABLE(LuauUnifierRecursionOnRestart, false) namespace Luau @@ -2974,10 +2973,7 @@ bool Unifier::occursCheck(TypePackId needle, TypePackId haystack, bool reversed) if (occurs) { reportError(location, OccursCheckFailed{}); - if (FFlag::LuauUnifierShouldNotCopyError) - log.replace(needle, BoundTypePack{builtinTypes->errorRecoveryTypePack()}); - else - log.replace(needle, *builtinTypes->errorRecoveryTypePack()); + log.replace(needle, BoundTypePack{builtinTypes->errorRecoveryTypePack()}); } return occurs; diff --git a/Ast/include/Luau/Parser.h b/Ast/include/Luau/Parser.h index 83d6eefda..5411379e3 100644 --- a/Ast/include/Luau/Parser.h +++ b/Ast/include/Luau/Parser.h @@ -146,7 +146,7 @@ class Parser AstStat* parseTypeAlias(const Location& start, bool exported); // type function Name ... end - AstStat* parseTypeFunction(const Location& start); + AstStat* parseTypeFunction(const Location& start, bool exported); AstDeclaredClassProp parseDeclaredClassMethod(); @@ -423,6 +423,7 @@ class Parser MatchLexeme endMismatchSuspect; std::vector functionStack; + size_t typeFunctionDepth = 0; DenseHashMap localMap; std::vector localStack; diff --git a/Ast/src/Parser.cpp b/Ast/src/Parser.cpp index 44a40abf4..5f6fcf5ea 100644 --- a/Ast/src/Parser.cpp +++ b/Ast/src/Parser.cpp @@ -19,7 +19,7 @@ LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100) LUAU_FASTFLAGVARIABLE(LuauSolverV2, false) LUAU_FASTFLAGVARIABLE(LuauNativeAttribute, false) LUAU_FASTFLAGVARIABLE(LuauAttributeSyntaxFunExpr, false) -LUAU_FASTFLAGVARIABLE(LuauUserDefinedTypeFunctionsSyntax, false) +LUAU_FASTFLAGVARIABLE(LuauUserDefinedTypeFunctionsSyntax2, false) LUAU_FASTFLAGVARIABLE(LuauAllowFragmentParsing, false) namespace Luau @@ -901,10 +901,10 @@ AstStat* Parser::parseReturn() AstStat* Parser::parseTypeAlias(const Location& start, bool exported) { // parsing a type function - if (FFlag::LuauUserDefinedTypeFunctionsSyntax) + if (FFlag::LuauUserDefinedTypeFunctionsSyntax2) { if (lexer.current().type == Lexeme::ReservedFunction) - return parseTypeFunction(start); + return parseTypeFunction(start, exported); } // parsing a type alias @@ -927,11 +927,14 @@ AstStat* Parser::parseTypeAlias(const Location& start, bool exported) } // type function Name `(' arglist `)' `=' funcbody `end' -AstStat* Parser::parseTypeFunction(const Location& start) +AstStat* Parser::parseTypeFunction(const Location& start, bool exported) { Lexeme matchFn = lexer.current(); nextLexeme(); + if (exported) + report(start, "Type function cannot be exported"); + // parse the name of the type function std::optional fnName = parseNameOpt("type function name"); if (!fnName) @@ -939,8 +942,13 @@ AstStat* Parser::parseTypeFunction(const Location& start) matchRecoveryStopOnToken[Lexeme::ReservedEnd]++; + size_t oldTypeFunctionDepth = typeFunctionDepth; + typeFunctionDepth = functionStack.size(); + AstExprFunction* body = parseFunctionBody(/* hasself */ false, matchFn, fnName->name, nullptr, AstArray({nullptr, 0})).first; + typeFunctionDepth = oldTypeFunctionDepth; + matchRecoveryStopOnToken[Lexeme::ReservedEnd]--; return allocator.alloc(Location(start, body->location), fnName->name, fnName->location, body); @@ -2291,6 +2299,12 @@ AstExpr* Parser::parseNameExpr(const char* context) { AstLocal* local = *value; + if (FFlag::LuauUserDefinedTypeFunctionsSyntax2) + { + if (local->functionDepth < typeFunctionDepth) + return reportExprError(lexer.current().location, {}, "Type function cannot reference outer local '%s'", local->name.value); + } + return allocator.alloc(name->location, local, local->functionDepth != functionStack.size() - 1); } diff --git a/CLI/FileUtils.cpp b/CLI/FileUtils.cpp index daa7c295a..e9f40a09a 100644 --- a/CLI/FileUtils.cpp +++ b/CLI/FileUtils.cpp @@ -57,12 +57,6 @@ bool isAbsolutePath(std::string_view path) #endif } -bool isExplicitlyRelative(std::string_view path) -{ - return (path == ".") || (path == "..") || (path.size() >= 2 && path[0] == '.' && path[1] == '/') || - (path.size() >= 3 && path[0] == '.' && path[1] == '.' && path[2] == '/'); -} - std::optional getCurrentWorkingDirectory() { // 2^17 - derived from the Windows path length limit @@ -353,6 +347,20 @@ bool traverseDirectory(const std::string& path, const std::function readFile(const std::string& name); std::optional readStdin(); bool isAbsolutePath(std::string_view path); -bool isExplicitlyRelative(std::string_view path); +bool isFile(const std::string& path); bool isDirectory(const std::string& path); bool traverseDirectory(const std::string& path, const std::function& callback); diff --git a/CLI/Repl.cpp b/CLI/Repl.cpp index b8e9d8142..8e7a84384 100644 --- a/CLI/Repl.cpp +++ b/CLI/Repl.cpp @@ -127,6 +127,8 @@ static int lua_require(lua_State* L) if (resolvedRequire.status == RequireResolver::ModuleStatus::Cached) return finishrequire(L); + else if (resolvedRequire.status == RequireResolver::ModuleStatus::Ambiguous) + luaL_errorL(L, "require path could not be resolved to a unique file"); else if (resolvedRequire.status == RequireResolver::ModuleStatus::NotFound) luaL_errorL(L, "error requiring module"); diff --git a/CLI/Require.cpp b/CLI/Require.cpp index b6753e963..9a00597af 100644 --- a/CLI/Require.cpp +++ b/CLI/Require.cpp @@ -24,6 +24,9 @@ RequireResolver::RequireResolver(lua_State* L, std::string path) std::replace(pathToResolve.begin(), pathToResolve.end(), '\\', '/'); + if (!isPrefixValid()) + luaL_argerrorL(L, 1, "require path must start with a valid prefix: ./, ../, or @"); + substituteAliasIfPresent(pathToResolve); } @@ -44,44 +47,14 @@ RequireResolver::ModuleStatus RequireResolver::findModule() // Put _MODULES table on stack for checking and saving to the cache luaL_findtable(L, LUA_REGISTRYINDEX, "_MODULES", 1); - RequireResolver::ModuleStatus moduleStatus = findModuleImpl(); - - if (moduleStatus != RequireResolver::ModuleStatus::NotFound) - return moduleStatus; - - if (!shouldSearchPathsArray()) - return moduleStatus; - - if (!isConfigFullyResolved) - parseNextConfig(); - - // Index-based iteration because std::iterator may be invalidated if config.paths is reallocated - for (size_t i = 0; i < config.paths.size(); ++i) - { - // "placeholder" acts as a requiring file in the relevant directory - std::optional absolutePathOpt = resolvePath(pathToResolve, joinPaths(config.paths[i], "placeholder")); - - if (!absolutePathOpt) - luaL_errorL(L, "error requiring module"); - - chunkname = *absolutePathOpt; - absolutePath = *absolutePathOpt; - - moduleStatus = findModuleImpl(); - - if (moduleStatus != RequireResolver::ModuleStatus::NotFound) - return moduleStatus; - - // Before finishing the loop, parse more config files if there are any - if (i == config.paths.size() - 1 && !isConfigFullyResolved) - parseNextConfig(); // could reallocate config.paths when paths are parsed and added - } - - return RequireResolver::ModuleStatus::NotFound; + return findModuleImpl(); } RequireResolver::ModuleStatus RequireResolver::findModuleImpl() { + if (isPathAmbiguous(absolutePath)) + return ModuleStatus::Ambiguous; + static const std::array possibleSuffixes = {".luau", ".lua", "/init.luau", "/init.lua"}; size_t unsuffixedAbsolutePathSize = absolutePath.size(); @@ -113,15 +86,34 @@ RequireResolver::ModuleStatus RequireResolver::findModuleImpl() return ModuleStatus::NotFound; } +bool RequireResolver::isPathAmbiguous(const std::string& path) +{ + bool found = false; + for (const char* suffix : {".luau", ".lua"}) + { + if (isFile(path + suffix)) + { + if (found) + return true; + else + found = true; + } + } + if (isDirectory(path) && found) + return true; + + return false; +} + bool RequireResolver::isRequireAllowed(std::string_view sourceChunkname) { LUAU_ASSERT(!sourceChunkname.empty()); return (sourceChunkname[0] == '=' || sourceChunkname[0] == '@'); } -bool RequireResolver::shouldSearchPathsArray() +bool RequireResolver::isPrefixValid() { - return !isAbsolutePath(pathToResolve) && !isExplicitlyRelative(pathToResolve); + return pathToResolve.compare(0, 2, "./") == 0 || pathToResolve.compare(0, 3, "../") == 0 || pathToResolve.compare(0, 1, "@") == 0; } void RequireResolver::resolveAndStoreDefaultPaths() @@ -283,24 +275,10 @@ void RequireResolver::parseConfigInDirectory(const std::string& directory) { std::string configPath = joinPaths(directory, Luau::kConfigName); - size_t numPaths = config.paths.size(); - if (std::optional contents = readFile(configPath)) { std::optional error = Luau::parseConfig(*contents, config); if (error) luaL_errorL(L, "error parsing %s (%s)", configPath.c_str(), (*error).c_str()); } - - // Resolve any newly obtained relative paths in "paths" in relation to configPath - for (auto it = config.paths.begin() + numPaths; it != config.paths.end(); ++it) - { - if (!isAbsolutePath(*it)) - { - if (std::optional resolvedPath = resolvePath(*it, configPath)) - *it = std::move(*resolvedPath); - else - luaL_errorL(L, "error requiring module"); - } - } } diff --git a/CLI/Require.h b/CLI/Require.h index ae96834f1..9c86c3cca 100644 --- a/CLI/Require.h +++ b/CLI/Require.h @@ -20,6 +20,7 @@ class RequireResolver { Cached, FileRead, + Ambiguous, NotFound }; @@ -46,10 +47,11 @@ class RequireResolver bool isConfigFullyResolved = false; bool isRequireAllowed(std::string_view sourceChunkname); - bool shouldSearchPathsArray(); + bool isPrefixValid(); void resolveAndStoreDefaultPaths(); ModuleStatus findModuleImpl(); + bool isPathAmbiguous(const std::string& path); std::optional getRequiringContextAbsolute(); std::string getRequiringContextRelative(); diff --git a/Compiler/src/Compiler.cpp b/Compiler/src/Compiler.cpp index 7ed70d14f..7fefe6076 100644 --- a/Compiler/src/Compiler.cpp +++ b/Compiler/src/Compiler.cpp @@ -3634,6 +3634,10 @@ struct Compiler { // do nothing } + else if (node->is()) + { + // do nothing + } else { LUAU_ASSERT(!"Unknown statement type"); diff --git a/Config/include/Luau/Config.h b/Config/include/Luau/Config.h index 6333c55a6..3866547bc 100644 --- a/Config/include/Luau/Config.h +++ b/Config/include/Luau/Config.h @@ -32,7 +32,6 @@ struct Config std::vector globals; - std::vector paths; std::unordered_map aliases; }; diff --git a/Config/src/Config.cpp b/Config/src/Config.cpp index 7d010265c..cf7d4b223 100644 --- a/Config/src/Config.cpp +++ b/Config/src/Config.cpp @@ -304,11 +304,6 @@ Error parseConfig(const std::string& contents, Config& config, bool compat) config.globals.push_back(value); return std::nullopt; } - else if (keys.size() == 1 && keys[0] == "paths") - { - config.paths.push_back(value); - return std::nullopt; - } else if (keys.size() == 2 && keys[0] == "aliases") return parseAlias(config.aliases, keys[1], value); else if (compat && keys.size() == 2 && keys[0] == "language" && keys[1] == "mode") diff --git a/tests/AstQuery.test.cpp b/tests/AstQuery.test.cpp index 6822ce6db..5fc51b39a 100644 --- a/tests/AstQuery.test.cpp +++ b/tests/AstQuery.test.cpp @@ -8,6 +8,8 @@ using namespace Luau; +LUAU_FASTFLAG(LuauDocumentationAtPosition) + struct DocumentationSymbolFixture : BuiltinsFixture { std::optional getDocSymbol(const std::string& source, Position position) @@ -163,6 +165,44 @@ TEST_CASE_FIXTURE(DocumentationSymbolFixture, "table_overloaded_function_prop") CHECK_EQ(symbol, "@test/global/Foo.new/overload/(string) -> number"); } +TEST_CASE_FIXTURE(DocumentationSymbolFixture, "string_metatable_method") +{ + ScopedFastFlag sff{FFlag::LuauDocumentationAtPosition, true}; + std::optional 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 symbol = getDocSymbol( + R"( + local x: Bar = Bar.new() + x:bar("asdf") + )", + Position(2, 11) + ); + + CHECK_EQ(symbol, "@test/globaltype/Foo.bar"); +} + TEST_SUITE_END(); TEST_SUITE_BEGIN("AstQuery"); diff --git a/tests/Compiler.test.cpp b/tests/Compiler.test.cpp index 48bd45d7e..73b8816f3 100644 --- a/tests/Compiler.test.cpp +++ b/tests/Compiler.test.cpp @@ -21,6 +21,7 @@ LUAU_FASTINT(LuauCompileInlineThresholdMaxBoost) LUAU_FASTINT(LuauCompileLoopUnrollThreshold) LUAU_FASTINT(LuauCompileLoopUnrollThresholdMaxBoost) LUAU_FASTINT(LuauRecursionLimit) +LUAU_FASTFLAG(LuauUserDefinedTypeFunctionsSyntax2) using namespace Luau; @@ -2796,6 +2797,16 @@ TEST_CASE("TypeAliasing") CHECK_NOTHROW(Luau::compileOrThrow(bcb, "type A = number local a: A = 1", options, parseOptions)); } +TEST_CASE("TypeFunction") +{ + ScopedFastFlag sff{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true}; + + Luau::BytecodeBuilder bcb; + Luau::CompileOptions options; + Luau::ParseOptions parseOptions; + CHECK_NOTHROW(Luau::compileOrThrow(bcb, "type function a() return types.any end", options, parseOptions)); +} + TEST_CASE("DebugLineInfo") { Luau::BytecodeBuilder bcb; diff --git a/tests/ConstraintGeneratorFixture.cpp b/tests/ConstraintGeneratorFixture.cpp index f595d6ec1..62efdb686 100644 --- a/tests/ConstraintGeneratorFixture.cpp +++ b/tests/ConstraintGeneratorFixture.cpp @@ -25,6 +25,7 @@ void ConstraintGeneratorFixture::generateConstraints(const std::string& code) cg = std::make_unique( mainModule, NotNull{&normalizer}, + NotNull{&typeFunctionRuntime}, NotNull(&moduleResolver), builtinTypes, NotNull(&ice), diff --git a/tests/ConstraintGeneratorFixture.h b/tests/ConstraintGeneratorFixture.h index acf616e00..782747c70 100644 --- a/tests/ConstraintGeneratorFixture.h +++ b/tests/ConstraintGeneratorFixture.h @@ -20,7 +20,8 @@ struct ConstraintGeneratorFixture : Fixture DcrLogger logger; UnifierSharedState sharedState{&ice}; Normalizer normalizer{&arena, builtinTypes, NotNull{&sharedState}}; - TypeFunctionRuntime typeFunctionRuntime; + TypeCheckLimits limits; + TypeFunctionRuntime typeFunctionRuntime{NotNull{&ice}, NotNull{&limits}}; std::unique_ptr dfg; std::unique_ptr cg; diff --git a/tests/Fixture.cpp b/tests/Fixture.cpp index 8f918f0c0..66e3ac306 100644 --- a/tests/Fixture.cpp +++ b/tests/Fixture.cpp @@ -598,6 +598,72 @@ BuiltinsFixture::BuiltinsFixture(bool freeze, bool prepareAutocomplete) Luau::freeze(frontend.globalsForAutocomplete.globalTypes); } +static std::vector parsePathExpr(const AstExpr& pathExpr) +{ + const AstExprIndexName* indexName = pathExpr.as(); + if (!indexName) + return {}; + + std::vector segments{indexName->index.value}; + + while (true) + { + if (AstExprIndexName* in = indexName->expr->as()) + { + segments.push_back(in->index.value); + indexName = in; + continue; + } + else if (AstExprGlobal* indexNameAsGlobal = indexName->expr->as()) + { + segments.push_back(indexNameAsGlobal->name.value); + break; + } + else if (AstExprLocal* indexNameAsLocal = indexName->expr->as()) + { + segments.push_back(indexNameAsLocal->local->name.value); + break; + } + else + return {}; + } + + std::reverse(segments.begin(), segments.end()); + return segments; +} + +std::optional pathExprToModuleName(const ModuleName& currentModuleName, const std::vector& segments) +{ + if (segments.empty()) + return std::nullopt; + + std::vector 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 pathExprToModuleName(const ModuleName& currentModuleName, const AstExpr& pathExpr) +{ + std::vector segments = parsePathExpr(pathExpr); + return pathExprToModuleName(currentModuleName, segments); +} + ModuleName fromString(std::string_view name) { return ModuleName(name); diff --git a/tests/Fixture.h b/tests/Fixture.h index 9b2db5b85..bd0db4efc 100644 --- a/tests/Fixture.h +++ b/tests/Fixture.h @@ -20,8 +20,10 @@ #include "doctest.h" #include +#include #include #include +#include namespace Luau { @@ -159,6 +161,9 @@ struct BuiltinsFixture : Fixture BuiltinsFixture(bool freeze = true, bool prepareAutocomplete = false); }; +std::optional pathExprToModuleName(const ModuleName& currentModuleName, const std::vector& segments); +std::optional pathExprToModuleName(const ModuleName& currentModuleName, const AstExpr& pathExpr); + ModuleName fromString(std::string_view name); template diff --git a/tests/Normalize.test.cpp b/tests/Normalize.test.cpp index 23b4f133a..d9c9f46d9 100644 --- a/tests/Normalize.test.cpp +++ b/tests/Normalize.test.cpp @@ -11,7 +11,6 @@ #include "Luau/BuiltinDefinitions.h" LUAU_FASTFLAG(LuauSolverV2) -LUAU_FASTFLAG(LuauNormalizeNotUnknownIntersection) LUAU_FASTINT(LuauTypeInferRecursionLimit) using namespace Luau; @@ -970,8 +969,6 @@ TEST_CASE_FIXTURE(NormalizeFixture, "non_final_types_can_be_normalized_but_are_n TEST_CASE_FIXTURE(NormalizeFixture, "intersect_with_not_unknown") { - ScopedFastFlag sff{FFlag::LuauNormalizeNotUnknownIntersection, true}; - TypeId notUnknown = arena.addType(NegationType{builtinTypes->unknownType}); TypeId type = arena.addType(IntersectionType{{builtinTypes->numberType, notUnknown}}); std::shared_ptr normalized = normalizer.normalize(type); diff --git a/tests/Parser.test.cpp b/tests/Parser.test.cpp index 74d7a9207..dea628594 100644 --- a/tests/Parser.test.cpp +++ b/tests/Parser.test.cpp @@ -17,7 +17,7 @@ LUAU_FASTINT(LuauTypeLengthLimit) LUAU_FASTINT(LuauParseErrorLimit) LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauAttributeSyntaxFunExpr) -LUAU_FASTFLAG(LuauUserDefinedTypeFunctionsSyntax) +LUAU_FASTFLAG(LuauUserDefinedTypeFunctionsSyntax2) namespace { @@ -2380,7 +2380,7 @@ TEST_CASE_FIXTURE(Fixture, "invalid_type_forms") TEST_CASE_FIXTURE(Fixture, "parse_user_defined_type_functions") { - ScopedFastFlag sff{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; + ScopedFastFlag sff{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true}; AstStat* stat = parse(R"( type function foo() @@ -2394,6 +2394,38 @@ TEST_CASE_FIXTURE(Fixture, "parse_user_defined_type_functions") REQUIRE(f->name == "foo"); } +TEST_CASE_FIXTURE(Fixture, "parse_nested_type_function") +{ + ScopedFastFlag sff{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true}; + + AstStat* stat = parse(R"( + local v1 = 1 + type function foo() + local v2 = 2 + local function bar() + v2 += 1 + type function inner() end + v2 += 2 + end + local function bar2() + v2 += 3 + end + end + local function bar() v1 += 1 end + )"); + + REQUIRE(stat != nullptr); +} + +TEST_CASE_FIXTURE(Fixture, "invalid_user_defined_type_functions") +{ + ScopedFastFlag sff{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true}; + + matchParseError("export type function foo() end", "Type function cannot be exported"); + matchParseError("local foo = 1; type function bar() print(foo) end", "Type function cannot reference outer local 'foo'"); + matchParseError("type function foo() local v1 = 1; type function bar() print(v1) end end", "Type function cannot reference outer local 'v1'"); +} + TEST_SUITE_END(); TEST_SUITE_BEGIN("ParseErrorRecovery"); diff --git a/tests/RequireByString.test.cpp b/tests/RequireByString.test.cpp index f76f9faff..641323c2d 100644 --- a/tests/RequireByString.test.cpp +++ b/tests/RequireByString.test.cpp @@ -308,6 +308,22 @@ TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireInitLua") assertOutputContainsAll({"true", "result from init.lua"}); } +TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireWithFileAmbiguity") +{ + std::string ambiguousPath = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/ambiguous_file_requirer"; + + runProtectedRequire(ambiguousPath); + assertOutputContainsAll({"false", "require path could not be resolved to a unique file"}); +} + +TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireWithDirectoryAmbiguity") +{ + std::string ambiguousPath = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/ambiguous_directory_requirer"; + + runProtectedRequire(ambiguousPath); + assertOutputContainsAll({"false", "require path could not be resolved to a unique file"}); +} + TEST_CASE_FIXTURE(ReplWithPathFixture, "CheckCacheAfterRequireLuau") { std::string relativePath = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/module"; @@ -401,25 +417,11 @@ TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireAbsolutePath") assertOutputContainsAll({"false", "cannot require an absolute path"}); } -TEST_CASE_FIXTURE(ReplWithPathFixture, "PathsArrayRelativePath") -{ - std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/with_config/src/requirer"; - runProtectedRequire(path); - assertOutputContainsAll({"true", "result from library"}); -} - -TEST_CASE_FIXTURE(ReplWithPathFixture, "PathsArrayExplicitlyRelativePath") -{ - std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/with_config/src/fail_requirer"; - runProtectedRequire(path); - assertOutputContainsAll({"false", "error requiring module"}); -} - -TEST_CASE_FIXTURE(ReplWithPathFixture, "PathsArrayFromParent") +TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireUnprefixedPath") { - std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/with_config/src/global_library_requirer"; + std::string path = "an/unprefixed/path"; runProtectedRequire(path); - assertOutputContainsAll({"true", "result from global_library"}); + assertOutputContainsAll({"false", "require path must start with a valid prefix: ./, ../, or @"}); } TEST_CASE_FIXTURE(ReplWithPathFixture, "RequirePathWithAlias") diff --git a/tests/Subtyping.test.cpp b/tests/Subtyping.test.cpp index 05bea2f73..27b2f6e7c 100644 --- a/tests/Subtyping.test.cpp +++ b/tests/Subtyping.test.cpp @@ -66,7 +66,8 @@ struct SubtypeFixture : Fixture InternalErrorReporter iceReporter; UnifierSharedState sharedState{&ice}; Normalizer normalizer{&arena, builtinTypes, NotNull{&sharedState}}; - TypeFunctionRuntime typeFunctionRuntime; + TypeCheckLimits limits; + TypeFunctionRuntime typeFunctionRuntime{NotNull{&iceReporter}, NotNull{&limits}}; ScopedFastFlag sff{FFlag::LuauSolverV2, true}; diff --git a/tests/ToString.test.cpp b/tests/ToString.test.cpp index 59d9b5fd7..fe87b6a70 100644 --- a/tests/ToString.test.cpp +++ b/tests/ToString.test.cpp @@ -13,7 +13,7 @@ using namespace Luau; LUAU_FASTFLAG(LuauRecursiveTypeParameterRestriction); LUAU_FASTFLAG(LuauSolverV2); LUAU_FASTFLAG(LuauAttributeSyntax); -LUAU_FASTFLAG(LuauUserDefinedTypeFunctions) +LUAU_FASTFLAG(LuauUserDefinedTypeFunctions2) TEST_SUITE_BEGIN("ToString"); @@ -964,12 +964,11 @@ TEST_CASE_FIXTURE(Fixture, "correct_stringification_user_defined_type_functions" std::vector{builtinTypes->numberType}, // Type Function Arguments {}, {AstName{"woohoo"}}, // Type Function Name - std::nullopt }; Type tv{tftt}; - if (FFlag::LuauSolverV2 && FFlag::LuauUserDefinedTypeFunctions) + if (FFlag::LuauSolverV2 && FFlag::LuauUserDefinedTypeFunctions2) CHECK_EQ(toString(&tv, {}), "woohoo"); } diff --git a/tests/Transpiler.test.cpp b/tests/Transpiler.test.cpp index f6208c1b1..188d9682d 100644 --- a/tests/Transpiler.test.cpp +++ b/tests/Transpiler.test.cpp @@ -12,7 +12,7 @@ using namespace Luau; -LUAU_FASTFLAG(LuauUserDefinedTypeFunctionsSyntax) +LUAU_FASTFLAG(LuauUserDefinedTypeFunctionsSyntax2) TEST_SUITE_BEGIN("TranspilerTests"); @@ -698,7 +698,7 @@ TEST_CASE_FIXTURE(Fixture, "transpile_string_literal_escape") TEST_CASE_FIXTURE(Fixture, "transpile_type_functions") { - ScopedFastFlag sff{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; + ScopedFastFlag sff{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true}; std::string code = R"( type function foo(arg1, arg2) if arg1 == arg2 then return arg1 end return arg2 end )"; diff --git a/tests/TypeFunction.test.cpp b/tests/TypeFunction.test.cpp index 18d8f17b8..fb53ee87d 100644 --- a/tests/TypeFunction.test.cpp +++ b/tests/TypeFunction.test.cpp @@ -13,7 +13,7 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) -LUAU_FASTFLAG(LuauUserDefinedTypeFunctions) +LUAU_FASTFLAG(LuauUserDefinedTypeFunctions2) LUAU_DYNAMIC_FASTINT(LuauTypeFamilyApplicationCartesianProductLimit) struct TypeFunctionFixture : Fixture @@ -1247,4 +1247,20 @@ TEST_CASE_FIXTURE(ClassFixture, "rawget_type_function_errors_w_classes") CHECK(toString(result.errors[0]) == "Property '\"BaseField\"' does not exist on type 'BaseClass'"); } +TEST_CASE_FIXTURE(Fixture, "fuzz_len_type_function_follow") +{ + // Should not fail assertions + check(R"( + local _ + _ = true + for l0=_,_,# _ do + end + for l0=_,_ do + if _ then + _ += _ + end + end + )"); +} + TEST_SUITE_END(); diff --git a/tests/TypeFunction.user.test.cpp b/tests/TypeFunction.user.test.cpp index fbce4df2f..72ba0fadd 100644 --- a/tests/TypeFunction.user.test.cpp +++ b/tests/TypeFunction.user.test.cpp @@ -8,16 +8,17 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) -LUAU_FASTFLAG(LuauUserDefinedTypeFunctionsSyntax) -LUAU_FASTFLAG(LuauUserDefinedTypeFunctions) +LUAU_FASTFLAG(LuauUserDefinedTypeFunctionsSyntax2) +LUAU_FASTFLAG(LuauUserDefinedTypeFunctions2) +LUAU_FASTFLAG(LuauUserDefinedTypeFunctionNoEvaluation) TEST_SUITE_BEGIN("UserDefinedTypeFunctionTests"); TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_nil_serialization_works") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; - ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; - ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; + ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true}; + ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true}; CheckResult result = check(R"( type function serialize_nil(arg) @@ -33,8 +34,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_nil_serialization_works") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_nil_methods_work") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; - ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; - ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; + ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true}; + ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true}; CheckResult result = check(R"( type function getnil() @@ -54,8 +55,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_nil_methods_work") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_unknown_serialization_works") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; - ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; - ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; + ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true}; + ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true}; CheckResult result = check(R"( type function serialize_unknown(arg) @@ -71,8 +72,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_unknown_serialization_works") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_unknown_methods_work") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; - ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; - ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; + ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true}; + ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true}; CheckResult result = check(R"( type function getunknown() @@ -92,8 +93,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_unknown_methods_work") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_never_serialization_works") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; - ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; - ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; + ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true}; + ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true}; CheckResult result = check(R"( type function serialize_never(arg) @@ -109,8 +110,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_never_serialization_works") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_never_methods_work") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; - ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; - ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; + ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true}; + ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true}; CheckResult result = check(R"( type function getnever() @@ -130,8 +131,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_never_methods_work") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_any_serialization_works") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; - ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; - ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; + ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true}; + ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true}; CheckResult result = check(R"( type function serialize_any(arg) @@ -147,8 +148,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_any_serialization_works") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_any_methods_work") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; - ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; - ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; + ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true}; + ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true}; CheckResult result = check(R"( type function getany() @@ -168,8 +169,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_any_methods_work") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_boolean_serialization_works") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; - ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; - ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; + ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true}; + ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true}; CheckResult result = check(R"( type function serialize_bool(arg) @@ -185,8 +186,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_boolean_serialization_works") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_boolean_methods_work") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; - ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; - ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; + ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true}; + ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true}; CheckResult result = check(R"( type function getboolean() @@ -206,8 +207,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_boolean_methods_work") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_number_serialization_works") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; - ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; - ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; + ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true}; + ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true}; CheckResult result = check(R"( type function serialize_num(arg) @@ -223,8 +224,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_number_serialization_works") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_number_methods_work") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; - ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; - ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; + ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true}; + ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true}; CheckResult result = check(R"( type function getnumber() @@ -244,8 +245,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_number_methods_work") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_string_serialization_works") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; - ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; - ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; + ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true}; + ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true}; CheckResult result = check(R"( type function serialize_str(arg) @@ -261,8 +262,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_string_serialization_works") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_string_methods_work") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; - ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; - ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; + ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true}; + ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true}; CheckResult result = check(R"( type function getstring() @@ -282,8 +283,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_string_methods_work") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_boolsingleton_serialization_works") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; - ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; - ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; + ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true}; + ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true}; CheckResult result = check(R"( type function serialize_boolsingleton(arg) @@ -299,8 +300,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_boolsingleton_serialization_works") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_boolsingleton_methods_work") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; - ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; - ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; + ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true}; + ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true}; CheckResult result = check(R"( type function getboolsingleton() @@ -320,8 +321,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_boolsingleton_methods_work") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_strsingleton_serialization_works") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; - ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; - ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; + ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true}; + ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true}; CheckResult result = check(R"( type function serialize_strsingleton(arg) @@ -337,8 +338,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_strsingleton_serialization_works") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_strsingleton_methods_work") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; - ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; - ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; + ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true}; + ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true}; CheckResult result = check(R"( type function getstrsingleton() @@ -358,8 +359,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_strsingleton_methods_work") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_union_serialization_works") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; - ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; - ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; + ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true}; + ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true}; CheckResult result = check(R"( type function serialize_union(arg) @@ -379,8 +380,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_union_serialization_works") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_union_methods_work") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; - ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; - ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; + ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true}; + ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true}; CheckResult result = check(R"( type function getunion() @@ -409,8 +410,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_union_methods_work") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_intersection_serialization_works") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; - ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; - ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; + ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true}; + ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true}; CheckResult result = check(R"( type function serialize_intersection(arg) @@ -430,8 +431,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_intersection_serialization_works") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_intersection_methods_work") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; - ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; - ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; + ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true}; + ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true}; CheckResult result = check(R"( type function getintersection() @@ -466,8 +467,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_intersection_methods_work") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_negation_methods_work") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; - ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; - ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; + ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true}; + ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true}; CheckResult result = check(R"( type function getnegation() @@ -492,8 +493,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_negation_methods_work") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_table_serialization_works") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; - ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; - ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; + ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true}; + ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true}; CheckResult result = check(R"( type function serialize_table(arg) @@ -513,8 +514,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_table_serialization_works") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_table_methods_work") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; - ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; - ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; + ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true}; + ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true}; CheckResult result = check(R"( type function gettable() @@ -553,8 +554,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_table_methods_work") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_metatable_methods_work") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; - ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; - ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; + ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true}; + ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true}; CheckResult result = check(R"( type function getmetatable() @@ -587,8 +588,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_metatable_methods_work") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_function_serialization_works") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; - ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; - ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; + ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true}; + ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true}; CheckResult result = check(R"( type function serialize_func(arg) @@ -604,8 +605,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_function_serialization_works") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_function_methods_work") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; - ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; - ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; + ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true}; + ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true}; CheckResult result = check(R"( type function getfunction() @@ -635,8 +636,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_function_methods_work") TEST_CASE_FIXTURE(ClassFixture, "udtf_class_serialization_works") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; - ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; - ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; + ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true}; + ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true}; CheckResult result = check(R"( type function serialize_class(arg) @@ -651,8 +652,8 @@ TEST_CASE_FIXTURE(ClassFixture, "udtf_class_serialization_works") TEST_CASE_FIXTURE(ClassFixture, "udtf_class_methods_works") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; - ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; - ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; + ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true}; + ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true}; CheckResult result = check(R"( @@ -675,8 +676,8 @@ TEST_CASE_FIXTURE(ClassFixture, "udtf_class_methods_works") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_check_mutability") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; - ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; - ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; + ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true}; + ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true}; CheckResult result = check(R"( type function checkmut() @@ -708,8 +709,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_check_mutability") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_copy_works") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; - ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; - ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; + ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true}; + ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true}; CheckResult result = check(R"( type function getcopy() @@ -742,8 +743,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_copy_works") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_simple_cyclic_serialization_works") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; - ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; - ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; + ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true}; + ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true}; CheckResult result = check(R"( type function serialize_cycle(arg) @@ -764,8 +765,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_simple_cyclic_serialization_works") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_createtable_bad_metatable") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; - ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; - ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; + ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true}; + ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true}; CheckResult result = check(R"( type function badmetatable() @@ -786,8 +787,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_createtable_bad_metatable") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_complex_cyclic_serialization_works") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; - ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; - ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; + ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true}; + ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true}; CheckResult result = check(R"( type function serialize_cycle2(arg) @@ -816,8 +817,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_complex_cyclic_serialization_works") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_user_error_is_reported") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; - ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; - ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; + ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true}; + ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true}; CheckResult result = check(R"( type function errors_if_string(arg) @@ -839,8 +840,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_user_error_is_reported") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_type_overrides_call_metamethod") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; - ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; - ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; + ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true}; + ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true}; CheckResult result = check(R"( type function hello(arg) @@ -858,8 +859,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_type_overrides_call_metamethod") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_type_overrides_eq_metamethod") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; - ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; - ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; + ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true}; + ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true}; CheckResult result = check(R"( type function hello() @@ -884,8 +885,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_type_overrides_eq_metamethod") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_function_type_cant_call_get_props") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; - ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; - ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; + ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true}; + ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true}; CheckResult result = check(R"( type function hello(arg) @@ -903,34 +904,62 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_function_type_cant_call_get_props") ); } -TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_cannot_call_other") +TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_calling_each_other") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; - ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; - ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; + ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true}; + ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true}; CheckResult result = check(R"( type function foo() return "hi" end - local x = true; - type function cannot_call_others() - return foo() + type function bar() + return types.singleton(foo()) end - local function ok(idx: cannot_call_others<>): string return idx end + local function ok(idx: bar<>): nil return idx end )"); - LUAU_CHECK_ERROR_COUNT(4, result); // There are 2 type function uninhabited error, 2 user defined type function error - UserDefinedTypeFunctionError* e = get(result.errors[0]); - REQUIRE(e); - CHECK(e->message == "'cannot_call_others' type function errored at runtime: [string \"cannot_call_others\"]:7: attempt to call a nil value"); + LUAU_CHECK_ERROR_COUNT(1, result); + TypePackMismatch* tpm = get(result.errors[0]); + REQUIRE(tpm); + CHECK(toString(tpm->givenTp) == "\"hi\""); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_no_shared_state") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true}; + ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true}; + + CheckResult result = check(R"( + type function foo() + if not glob then + glob = 'a' + else + glob ..= 'b' + end + + return glob + end + type function bar(prefix) + return types.singleton(prefix:value() .. foo()) + end + local function ok1(idx: bar<'x'>): nil return idx end + local function ok2(idx: bar<'y'>): nil return idx end + )"); + + // We are only checking first errors, others are mostly duplicates + LUAU_CHECK_ERROR_COUNT(8, result); + CHECK(toString(result.errors[0]) == R"('bar' type function errored at runtime: [string "foo"]:4: attempt to modify a readonly table)"); + CHECK(toString(result.errors[1]) == R"(Type function instance bar<"x"> is uninhabited)"); } TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_optionify") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; - ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; - ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; + ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true}; + ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true}; CheckResult result = check(R"( type function optionify(tbl) @@ -959,8 +988,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_optionify") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_calling_illegal_global") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; - ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; - ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions, true}; + ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true}; + ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true}; CheckResult result = check(R"( type function illegal(arg) @@ -980,9 +1009,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_calling_illegal_global") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_recursion_and_gc") { - ScopedFastFlag newSolver{ FFlag::LuauSolverV2, true }; - ScopedFastFlag udtfSyntax{ FFlag::LuauUserDefinedTypeFunctionsSyntax, true }; - ScopedFastFlag udtf{ FFlag::LuauUserDefinedTypeFunctions, true }; + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag udtfSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true}; + ScopedFastFlag udtf{FFlag::LuauUserDefinedTypeFunctions2, true}; CheckResult result = check(R"( type function foo(tbl) @@ -1004,4 +1033,72 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_recursion_and_gc") REQUIRE(tpm); } +TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_recovery_no_upvalues") +{ + ScopedFastFlag solverV2{FFlag::LuauSolverV2, true}; + ScopedFastFlag userDefinedTypeFunctionsSyntax{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true}; + ScopedFastFlag userDefinedTypeFunctions{FFlag::LuauUserDefinedTypeFunctions2, true}; + ScopedFastFlag userDefinedTypeFunctionNoEvaluation{FFlag::LuauUserDefinedTypeFunctionNoEvaluation, true}; + + CheckResult result = check(R"( + local var + + type function save_upvalue(arg) + var = 1 + return arg + end + + type test = "test" + local function ok(idx: save_upvalue): "test" + 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): never return tbl end + )"); + + LUAU_REQUIRE_ERROR_COUNT(1, result); + TypePackMismatch* tpm = get(result.errors[0]); + REQUIRE(tpm); + CHECK(toString(tpm->givenTp) == "{ foo: string }"); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.aliases.test.cpp b/tests/TypeInfer.aliases.test.cpp index 15eed3927..53f134f30 100644 --- a/tests/TypeInfer.aliases.test.cpp +++ b/tests/TypeInfer.aliases.test.cpp @@ -9,8 +9,8 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) -LUAU_FASTFLAG(LuauUserDefinedTypeFunctionsSyntax) -LUAU_FASTFLAG(LuauUserDefinedTypeFunctions) +LUAU_FASTFLAG(LuauUserDefinedTypeFunctionsSyntax2) +LUAU_FASTFLAG(LuauUserDefinedTypeFunctions2) TEST_SUITE_BEGIN("TypeAliases"); @@ -1156,7 +1156,7 @@ type Foo = Foo | string TEST_CASE_FIXTURE(BuiltinsFixture, "type_alias_adds_reduce_constraint_for_type_function") { - if (!FFlag::LuauSolverV2 || !FFlag::LuauUserDefinedTypeFunctions) + if (!FFlag::LuauSolverV2 || !FFlag::LuauUserDefinedTypeFunctions2) return; CheckResult result = check(R"( @@ -1170,8 +1170,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "type_alias_adds_reduce_constraint_for_type_f TEST_CASE_FIXTURE(Fixture, "user_defined_type_function_errors") { - ScopedFastFlag sff{FFlag::LuauUserDefinedTypeFunctionsSyntax, true}; - ScopedFastFlag noUDTFimpl{FFlag::LuauUserDefinedTypeFunctions, false}; + ScopedFastFlag sff{FFlag::LuauUserDefinedTypeFunctionsSyntax2, true}; + ScopedFastFlag noUDTFimpl{FFlag::LuauUserDefinedTypeFunctions2, false}; CheckResult result = check(R"( type function foo() @@ -1182,4 +1182,18 @@ TEST_CASE_FIXTURE(Fixture, "user_defined_type_function_errors") CHECK(toString(result.errors[0]) == "This syntax is not supported"); } +TEST_CASE_FIXTURE(Fixture, "bound_type_in_alias_segfault") +{ + ScopedFastFlag sff{FFlag::LuauSolverV2, true}; + + LUAU_CHECK_NO_ERRORS(check(R"( + --!nonstrict + type Map = {[ K]: V} + function foo:bar(): Config end + type Config = Map & { fields: FieldConfigMap} + export type FieldConfig = {[ string]: any} + export type FieldConfigMap = Map> + )")); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.functions.test.cpp b/tests/TypeInfer.functions.test.cpp index c76745c72..3803df7c1 100644 --- a/tests/TypeInfer.functions.test.cpp +++ b/tests/TypeInfer.functions.test.cpp @@ -20,8 +20,6 @@ LUAU_FASTFLAG(LuauInstantiateInSubtyping); LUAU_FASTFLAG(LuauSolverV2); LUAU_FASTINT(LuauTarjanChildLimit); -LUAU_DYNAMIC_FASTFLAG(LuauImproveNonFunctionCallError) - TEST_SUITE_BEGIN("TypeInferFunctions"); TEST_CASE_FIXTURE(Fixture, "general_case_table_literal_blocks") @@ -2340,20 +2338,10 @@ TEST_CASE_FIXTURE(Fixture, "attempt_to_call_an_intersection_of_tables") LUAU_REQUIRE_ERROR_COUNT(1, result); - if (DFFlag::LuauImproveNonFunctionCallError) - { - if (FFlag::LuauSolverV2) - CHECK_EQ(toString(result.errors[0]), "Cannot call a value of type { x: number } & { y: string }"); - else - CHECK_EQ(toString(result.errors[0]), "Cannot call a value of type {| x: number |}"); - } + if (FFlag::LuauSolverV2) + CHECK_EQ(toString(result.errors[0]), "Cannot call a value of type { x: number } & { y: string }"); else - { - if (FFlag::LuauSolverV2) - CHECK_EQ(toString(result.errors[0]), "Cannot call non-function { x: number } & { y: string }"); - else - CHECK_EQ(toString(result.errors[0]), "Cannot call non-function {| x: number |}"); - } + CHECK_EQ(toString(result.errors[0]), "Cannot call a value of type {| x: number |}"); } TEST_CASE_FIXTURE(BuiltinsFixture, "attempt_to_call_an_intersection_of_tables_with_call_metamethod") @@ -2845,17 +2833,12 @@ TEST_CASE_FIXTURE(Fixture, "cannot_call_union_of_functions") LUAU_REQUIRE_ERROR_COUNT(1, result); - if (DFFlag::LuauImproveNonFunctionCallError) - { - std::string expected = R"(Cannot call a value of the union type: + std::string expected = R"(Cannot call a value of the union type: | () -> () | () -> () -> () We are unable to determine the appropriate result type for such a call.)"; - CHECK(expected == toString(result.errors[0])); - } - else - CHECK("Cannot call non-function (() -> () -> ()) | (() -> ())" == toString(result.errors[0])); + CHECK(expected == toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "fuzzer_missing_follow_in_ast_stat_fun") diff --git a/tests/TypeInfer.loops.test.cpp b/tests/TypeInfer.loops.test.cpp index ec36b30eb..0498437a9 100644 --- a/tests/TypeInfer.loops.test.cpp +++ b/tests/TypeInfer.loops.test.cpp @@ -16,8 +16,6 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) -LUAU_DYNAMIC_FASTFLAG(LuauImproveNonFunctionCallError) - TEST_SUITE_BEGIN("TypeInferLoops"); TEST_CASE_FIXTURE(Fixture, "for_loop") @@ -194,10 +192,7 @@ TEST_CASE_FIXTURE(Fixture, "for_in_loop_should_fail_with_non_function_iterator") LUAU_REQUIRE_ERROR_COUNT(1, result); - if (DFFlag::LuauImproveNonFunctionCallError) - CHECK_EQ("Cannot call a value of type string", toString(result.errors[0])); - else - CHECK_EQ("Cannot call non-function string", toString(result.errors[0])); + CHECK_EQ("Cannot call a value of type string", toString(result.errors[0])); } TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_with_just_one_iterator_is_ok") diff --git a/tests/TypeInfer.modules.test.cpp b/tests/TypeInfer.modules.test.cpp index ba54aca07..f8cc88315 100644 --- a/tests/TypeInfer.modules.test.cpp +++ b/tests/TypeInfer.modules.test.cpp @@ -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(); diff --git a/tests/TypeInfer.operators.test.cpp b/tests/TypeInfer.operators.test.cpp index 18c3410d3..d0715669d 100644 --- a/tests/TypeInfer.operators.test.cpp +++ b/tests/TypeInfer.operators.test.cpp @@ -17,7 +17,6 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) -LUAU_FASTFLAG(LuauRemoveBadRelationalOperatorWarning) TEST_SUITE_BEGIN("TypeInferOperators"); @@ -860,7 +859,7 @@ TEST_CASE_FIXTURE(Fixture, "error_on_invalid_operand_types_to_relational_operato )"); // If DCR is off and the flag to remove this check in the old solver is on, the expected behavior is no errors. - if (!FFlag::LuauSolverV2 && FFlag::LuauRemoveBadRelationalOperatorWarning) + if (!FFlag::LuauSolverV2) { LUAU_REQUIRE_NO_ERRORS(result); return; @@ -1578,10 +1577,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "compare_singleton_string_to_string") // There is a flag to gate turning this off, and this warning is not // implemented in the new solver, so assert there are no errors. - if (FFlag::LuauRemoveBadRelationalOperatorWarning || FFlag::LuauSolverV2) - LUAU_REQUIRE_NO_ERRORS(result); - else - LUAU_REQUIRE_ERROR_COUNT(1, result); + LUAU_REQUIRE_NO_ERRORS(result); } TEST_CASE_FIXTURE(BuiltinsFixture, "no_infinite_expansion_of_free_type" * doctest::timeout(1.0)) diff --git a/tests/TypeInfer.refinements.test.cpp b/tests/TypeInfer.refinements.test.cpp index 417973844..2943486d3 100644 --- a/tests/TypeInfer.refinements.test.cpp +++ b/tests/TypeInfer.refinements.test.cpp @@ -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(); diff --git a/tests/TypeInfer.tables.test.cpp b/tests/TypeInfer.tables.test.cpp index 46c1d1c1c..eef76cab8 100644 --- a/tests/TypeInfer.tables.test.cpp +++ b/tests/TypeInfer.tables.test.cpp @@ -20,7 +20,6 @@ LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauFixIndexerSubtypingOrdering) LUAU_FASTFLAG(LuauAcceptIndexingTableUnionsIntersections) -LUAU_DYNAMIC_FASTFLAG(LuauImproveNonFunctionCallError) LUAU_DYNAMIC_FASTINT(LuauTypeSolverRelease) TEST_SUITE_BEGIN("TableTests"); @@ -2407,7 +2406,7 @@ could not be converted into // // Second, nil <: unknown, so we consider that parameter to be optional. LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK("Type 'b1' could not be converted into 'a1'; at [read \"y\"], string is not exactly number" == toString(result.errors[0])); + CHECK("Type 'b1' could not be converted into 'a1'; at table()[read \"y\"], string is not exactly number" == toString(result.errors[0])); } else if (FFlag::LuauInstantiateInSubtyping) { @@ -2583,10 +2582,7 @@ b() LUAU_REQUIRE_ERROR_COUNT(1, result); - if (DFFlag::LuauImproveNonFunctionCallError) - CHECK_EQ(toString(result.errors[0]), R"(Cannot call a value of type t1 where t1 = { @metatable { __call: t1 }, { } })"); - else - CHECK_EQ(toString(result.errors[0]), R"(Cannot call non-function t1 where t1 = { @metatable { __call: t1 }, { } })"); + CHECK_EQ(toString(result.errors[0]), R"(Cannot call a value of type t1 where t1 = { @metatable { __call: t1 }, { } })"); } TEST_CASE_FIXTURE(Fixture, "table_subtyping_shouldn't_add_optional_properties_to_sealed_tables") @@ -3265,7 +3261,11 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_call_metamethod_must_be_callable") LUAU_REQUIRE_ERROR_COUNT(1, result); - if (!FFlag::LuauSolverV2) + if (FFlag::LuauSolverV2) + { + CHECK("Cannot call a value of type a" == toString(result.errors[0])); + } + else { TypeError e{ Location{{5, 20}, {5, 21}}, @@ -3273,14 +3273,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_call_metamethod_must_be_callable") }; CHECK(result.errors[0] == e); } - else if (DFFlag::LuauImproveNonFunctionCallError) - { - CHECK("Cannot call a value of type a" == toString(result.errors[0])); - } - else - { - CHECK("Cannot call non-function a" == toString(result.errors[0])); - } } TEST_CASE_FIXTURE(BuiltinsFixture, "table_call_metamethod_generic") @@ -4851,4 +4843,27 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "length_of_array_is_number") LUAU_REQUIRE_NO_ERRORS(result); } +TEST_CASE_FIXTURE(BuiltinsFixture, "subtyping_with_a_metatable_table_path") +{ + // Builtin functions have to be setup for the new solver + if (!FFlag::LuauSolverV2) + return; + + CheckResult result = check(R"( + type self = {} & {} + type Class = typeof(setmetatable()) + local function _(): Class + return setmetatable({}::self, {}) + end + )"); + + LUAU_REQUIRE_ERROR_COUNT(1, result); + CHECK_EQ( + "Type pack '{ @metatable { }, { } & { } }' could not be converted into 'Class'; at [0].metatable(), { } is not a subtype of nil\n" + "\ttype { @metatable { }, { } & { } }[0].table()[0] ({ }) is not a subtype of Class[0].table() (nil)\n" + "\ttype { @metatable { }, { } & { } }[0].table()[1] ({ }) is not a subtype of Class[0].table() (nil)", + toString(result.errors[0]) + ); +} + TEST_SUITE_END(); diff --git a/tests/TypePath.test.cpp b/tests/TypePath.test.cpp index 2481f27a1..be6e84faf 100644 --- a/tests/TypePath.test.cpp +++ b/tests/TypePath.test.cpp @@ -235,6 +235,23 @@ TEST_CASE_FIXTURE(ClassFixture, "metatables") } SUBCASE("table") + { + TYPESOLVE_CODE(R"( + type Table = { foo: number } + type Metatable = { bar: number } + local tbl: Table = { foo = 123 } + local mt: Metatable = { bar = 456 } + local res = setmetatable(tbl, mt) + )"); + + // Tricky test setup because 'setmetatable' mutates the argument 'tbl' type + auto result = traverseForType(requireType("res"), Path(TypeField::Table), builtinTypes); + auto expected = lookupType("Table"); + REQUIRE(expected); + CHECK(result == follow(*expected)); + } + + SUBCASE("metatable") { TYPESOLVE_CODE(R"( local mt = { foo = 123 } diff --git a/tests/require/with_config/.luaurc b/tests/require/with_config/.luaurc index 28ebca118..7e7abf18a 100644 --- a/tests/require/with_config/.luaurc +++ b/tests/require/with_config/.luaurc @@ -1,5 +1,4 @@ { - "paths": ["GlobalLuauLibraries"], "aliases": { "dep": "this_should_be_overwritten_by_child_luaurc", "otherdep": "src/other_dependency" diff --git a/tests/require/with_config/src/.luaurc b/tests/require/with_config/src/.luaurc index 8c1ae683b..90c6b646d 100644 --- a/tests/require/with_config/src/.luaurc +++ b/tests/require/with_config/src/.luaurc @@ -1,5 +1,4 @@ { - "paths": ["../ProjectLuauLibraries"], "aliases": { "dep": "dependency", "subdir": "subdirectory" diff --git a/tests/require/with_config/src/fail_requirer.luau b/tests/require/with_config/src/fail_requirer.luau deleted file mode 100644 index 0454f9221..000000000 --- a/tests/require/with_config/src/fail_requirer.luau +++ /dev/null @@ -1,2 +0,0 @@ --- shouldn't attempt to search paths array because of "./" prefix -return require("./library") diff --git a/tests/require/with_config/src/global_library_requirer.luau b/tests/require/with_config/src/global_library_requirer.luau deleted file mode 100644 index 747e14f5f..000000000 --- a/tests/require/with_config/src/global_library_requirer.luau +++ /dev/null @@ -1,2 +0,0 @@ --- should be required using the paths array in the parent directory's .luaurc -return require("global_library") diff --git a/tests/require/with_config/src/requirer.luau b/tests/require/with_config/src/requirer.luau deleted file mode 100644 index 67028abb2..000000000 --- a/tests/require/with_config/src/requirer.luau +++ /dev/null @@ -1,2 +0,0 @@ --- should be required using the paths array in .luaurc -return require("library") diff --git a/tests/require/without_config/ambiguous/directory/dependency.luau b/tests/require/without_config/ambiguous/directory/dependency.luau new file mode 100644 index 000000000..07466f429 --- /dev/null +++ b/tests/require/without_config/ambiguous/directory/dependency.luau @@ -0,0 +1 @@ +return {"result from dependency"} diff --git a/tests/require/without_config/ambiguous/directory/dependency/init.luau b/tests/require/without_config/ambiguous/directory/dependency/init.luau new file mode 100644 index 000000000..07466f429 --- /dev/null +++ b/tests/require/without_config/ambiguous/directory/dependency/init.luau @@ -0,0 +1 @@ +return {"result from dependency"} diff --git a/tests/require/without_config/ambiguous/file/dependency.lua b/tests/require/without_config/ambiguous/file/dependency.lua new file mode 100644 index 000000000..07466f429 --- /dev/null +++ b/tests/require/without_config/ambiguous/file/dependency.lua @@ -0,0 +1 @@ +return {"result from dependency"} diff --git a/tests/require/without_config/ambiguous/file/dependency.luau b/tests/require/without_config/ambiguous/file/dependency.luau new file mode 100644 index 000000000..07466f429 --- /dev/null +++ b/tests/require/without_config/ambiguous/file/dependency.luau @@ -0,0 +1 @@ +return {"result from dependency"} diff --git a/tests/require/without_config/ambiguous_directory_requirer.luau b/tests/require/without_config/ambiguous_directory_requirer.luau new file mode 100644 index 000000000..e46be806b --- /dev/null +++ b/tests/require/without_config/ambiguous_directory_requirer.luau @@ -0,0 +1,3 @@ +local result = require("./ambiguous/directory/dependency") +result[#result+1] = "required into module" +return result diff --git a/tests/require/without_config/ambiguous_file_requirer.luau b/tests/require/without_config/ambiguous_file_requirer.luau new file mode 100644 index 000000000..8e3a576d3 --- /dev/null +++ b/tests/require/without_config/ambiguous_file_requirer.luau @@ -0,0 +1,3 @@ +local result = require("./ambiguous/file/dependency") +result[#result+1] = "required into module" +return result diff --git a/tests/require/without_config/module.luau b/tests/require/without_config/module.luau index 94826b66a..1d1393ff8 100644 --- a/tests/require/without_config/module.luau +++ b/tests/require/without_config/module.luau @@ -1,3 +1,3 @@ -local result = require("dependency") +local result = require("./dependency") result[#result+1] = "required into module" return result