Sync to upstream/release/679 (#1884)

# What's Changed?

We've been hard at work fixing bugs and introducing new features!

## VM 
* Include constant-folding information in Luau cost model for inlining
and loop unrolling
   * ~1% improvement in compile times

## New Type Solver
* `Luau::shallowClone`'s last argument, whether to clone persistent
(builtin) types, is now non-optional.
* Refinements on properties of tables are now computed with a `read`
table property. This resolves some issues around refining table
properies and then trying to set them. Fixes #1344. Fixes #1651.
```
if foo.bar then
    -- Prior to this release, this would be `typeof(foo) & { bar: ~(false?) }
    -- Now, this is `typeof(foo) & { read bar: ~(false?) }
end
```
* The type function `keyof` should respect the empty string as a
property, as in:
```
-- equivalent to type Foo =""
type Foo = keyof<{ [""]: number }>
```
* Descend into literals to report subtyping errors for function calls:
this both improves bidirectional inference and makes errors more
specific. Before, the error reporting for a table with incorrect members
passed to a function would cite the entire table, but now it only cites
the members that are incorrectly typed.
* Fixes a case where intersecting two tables without any common
properties would create `never`, instead of a table with both of their
properties.

# Internal Contributors
Co-authored-by: Ariel Weiss <aaronweiss@roblox.com>
Co-authored-by: Hunter Goldstein <hgoldstein@roblox.com>
Co-authored-by: James McNellis <jmcnellis@roblox.com>
Co-authored-by: Sora Kanosue <skanosue@roblox.com>
Co-authored-by: Talha Pathan <tpathan@roblox.com>
Co-authored-by: Vighnesh Vijay <vvijay@roblox.com>
Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>

---------

Co-authored-by: Hunter Goldstein <hgoldstein@roblox.com>
Co-authored-by: Varun Saini <61795485+vrn-sn@users.noreply.github.com>
Co-authored-by: Alexander Youngblood <ayoungblood@roblox.com>
Co-authored-by: Aviral Goel <agoel@roblox.com>
Co-authored-by: Vighnesh <vvijay@roblox.com>
Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
Co-authored-by: Ariel Weiss <aaronweiss@roblox.com>
Co-authored-by: Andy Friesen <afriesen@roblox.com>
This commit is contained in:
menarulalam 2025-06-20 18:55:42 -04:00 committed by GitHub
parent 713ee2ff8b
commit 8fe64db609
Signed by: DevComp
GPG key ID: B5690EEEBB952194
87 changed files with 3202 additions and 4077 deletions

View file

@ -29,11 +29,10 @@ struct CloneState
* Be mindful about which behavior you actually _want_. * Be mindful about which behavior you actually _want_.
* *
* Persistent types are not cloned as an optimization. * Persistent types are not cloned as an optimization.
* If a type is cloned in order to mutate it, 'ignorePersistent' has to be set * If a type is cloned in order to mutate it, 'clonePersistentTypes' has to be set
*/ */
TypePackId shallowClone(TypePackId tp, TypeArena& dest, CloneState& cloneState, bool clonePersistentTypes);
TypePackId shallowClone(TypePackId tp, TypeArena& dest, CloneState& cloneState, bool ignorePersistent = false); TypeId shallowClone(TypeId typeId, TypeArena& dest, CloneState& cloneState, bool clonePersistentTypes);
TypeId shallowClone(TypeId typeId, TypeArena& dest, CloneState& cloneState, bool ignorePersistent = false);
TypePackId clone(TypePackId tp, TypeArena& dest, CloneState& cloneState); TypePackId clone(TypePackId tp, TypeArena& dest, CloneState& cloneState);
TypeId clone(TypeId tp, TypeArena& dest, CloneState& cloneState); TypeId clone(TypeId tp, TypeArena& dest, CloneState& cloneState);

View file

@ -143,8 +143,8 @@ struct ConstraintGenerator
NotNull<ModuleResolver> moduleResolver, NotNull<ModuleResolver> moduleResolver,
NotNull<BuiltinTypes> builtinTypes, NotNull<BuiltinTypes> builtinTypes,
NotNull<InternalErrorReporter> ice, NotNull<InternalErrorReporter> ice,
const ScopePtr& globalScope, ScopePtr globalScope,
const ScopePtr& typeFunctionScope, ScopePtr typeFunctionScope,
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope, std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope,
DcrLogger* logger, DcrLogger* logger,
NotNull<DataFlowGraph> dfg, NotNull<DataFlowGraph> dfg,

View file

@ -9,7 +9,6 @@
#include "Luau/Scope.h" #include "Luau/Scope.h"
#include "Luau/Set.h" #include "Luau/Set.h"
#include "Luau/TypeCheckLimits.h" #include "Luau/TypeCheckLimits.h"
#include "Luau/Variant.h"
#include <mutex> #include <mutex>
#include <string> #include <string>
@ -116,6 +115,8 @@ struct FrontendOptions
// An optional callback which is called for every *dirty* module was checked // An optional callback which is called for every *dirty* module was checked
// Is multi-threaded typechecking is used, this callback might be called from multiple threads and has to be thread-safe // Is multi-threaded typechecking is used, this callback might be called from multiple threads and has to be thread-safe
std::function<void(const SourceModule& sourceModule, const Luau::Module& module)> customModuleCheck; std::function<void(const SourceModule& sourceModule, const Luau::Module& module)> customModuleCheck;
bool collectTypeAllocationStats = false;
}; };
struct CheckResult struct CheckResult
@ -139,6 +140,7 @@ struct FrontendModuleResolver : ModuleResolver
bool setModule(const ModuleName& moduleName, ModulePtr module); bool setModule(const ModuleName& moduleName, ModulePtr module);
void clearModules(); void clearModules();
private: private:
Frontend* frontend; Frontend* frontend;
@ -156,6 +158,13 @@ struct Frontend
size_t filesStrict = 0; size_t filesStrict = 0;
size_t filesNonstrict = 0; size_t filesNonstrict = 0;
size_t typesAllocated = 0;
size_t typePacksAllocated = 0;
size_t boolSingletonsMinted = 0;
size_t strSingletonsMinted = 0;
size_t uniqueStrSingletonsMinted = 0;
double timeRead = 0; double timeRead = 0;
double timeParse = 0; double timeParse = 0;
double timeCheck = 0; double timeCheck = 0;
@ -164,6 +173,11 @@ struct Frontend
Frontend(FileResolver* fileResolver, ConfigResolver* configResolver, const FrontendOptions& options = {}); Frontend(FileResolver* fileResolver, ConfigResolver* configResolver, const FrontendOptions& options = {});
void setLuauSolverSelectionFromWorkspace(bool newSolverEnabled);
bool getLuauSolverSelection() const;
bool getLuauSolverSelectionFlagged() const;
// The default value assuming there is no workspace setup yet
std::atomic<bool> useNewLuauSolver{FFlag::LuauSolverV2};
// Parse module graph and prepare SourceNode/SourceModule data, including required dependencies without running typechecking // Parse module graph and prepare SourceNode/SourceModule data, including required dependencies without running typechecking
void parse(const ModuleName& name); void parse(const ModuleName& name);
void parseModules(const std::vector<ModuleName>& name); void parseModules(const std::vector<ModuleName>& name);

View file

@ -5,6 +5,8 @@
#include "Luau/DenseHash.h" #include "Luau/DenseHash.h"
#include "Luau/NotNull.h" #include "Luau/NotNull.h"
#include "Luau/TypeFwd.h" #include "Luau/TypeFwd.h"
#include <optional>
#include <set> #include <set>
namespace Luau namespace Luau
@ -27,6 +29,13 @@ SimplifyResult simplifyUnion(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeAre
SimplifyResult simplifyIntersectWithTruthy(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, TypeId target); SimplifyResult simplifyIntersectWithTruthy(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, TypeId target);
SimplifyResult simplifyIntersectWithFalsy(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, TypeId target); SimplifyResult simplifyIntersectWithFalsy(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, TypeId target);
std::optional<TypeId> intersectWithSimpleDiscriminant(
NotNull<BuiltinTypes> builtinTypes,
NotNull<TypeArena> arena,
TypeId target,
TypeId discriminant
);
enum class Relation enum class Relation
{ {
Disjoint, // No A is a B or vice versa Disjoint, // No A is a B or vice versa

View file

@ -456,9 +456,8 @@ struct Property
std::optional<Location> typeLocation = std::nullopt std::optional<Location> typeLocation = std::nullopt
); );
// DEPRECATED: Should only be called in non-RWP! We assert that the `readTy` is not nullopt. // DEPRECATED: This should be removed with `LuauTypeSolverV2` clean up
// TODO: Kill once we don't have non-RWP. TypeId type_DEPRECATED() const;
TypeId type() const;
void setType(TypeId ty); void setType(TypeId ty);
// If this property has a present `writeTy`, set it equal to the `readTy`. // If this property has a present `writeTy`, set it equal to the `readTy`.
@ -565,13 +564,13 @@ struct ExternType
ModuleName definitionModuleName, ModuleName definitionModuleName,
std::optional<Location> definitionLocation std::optional<Location> definitionLocation
) )
: name(name) : name(std::move(name))
, props(props) , props(std::move(props))
, parent(parent) , parent(parent)
, metatable(metatable) , metatable(metatable)
, tags(tags) , tags(std::move(tags))
, userData(userData) , userData(std::move(userData))
, definitionModuleName(definitionModuleName) , definitionModuleName(std::move(definitionModuleName))
, definitionLocation(definitionLocation) , definitionLocation(definitionLocation)
{ {
} }
@ -587,13 +586,13 @@ struct ExternType
std::optional<Location> definitionLocation, std::optional<Location> definitionLocation,
std::optional<TableIndexer> indexer std::optional<TableIndexer> indexer
) )
: name(name) : name(std::move(name))
, props(props) , props(std::move(props))
, parent(parent) , parent(parent)
, metatable(metatable) , metatable(metatable)
, tags(tags) , tags(std::move(tags))
, userData(userData) , userData(std::move(userData))
, definitionModuleName(definitionModuleName) , definitionModuleName(std::move(definitionModuleName))
, definitionLocation(definitionLocation) , definitionLocation(definitionLocation)
, indexer(indexer) , indexer(indexer)
{ {
@ -638,31 +637,31 @@ struct TypeFunctionInstanceType
UserDefinedFunctionData userFuncData UserDefinedFunctionData userFuncData
) )
: function(function) : function(function)
, typeArguments(typeArguments) , typeArguments(std::move(typeArguments))
, packArguments(packArguments) , packArguments(std::move(packArguments))
, userFuncName(userFuncName) , userFuncName(userFuncName)
, userFuncData(userFuncData) , userFuncData(std::move(userFuncData))
{ {
} }
TypeFunctionInstanceType(const TypeFunction& function, std::vector<TypeId> typeArguments) TypeFunctionInstanceType(const TypeFunction& function, std::vector<TypeId> typeArguments)
: function{&function} : function{&function}
, typeArguments(typeArguments) , typeArguments(std::move(typeArguments))
, packArguments{} , packArguments{}
{ {
} }
TypeFunctionInstanceType(const TypeFunction& function, std::vector<TypeId> typeArguments, std::vector<TypePackId> packArguments) TypeFunctionInstanceType(const TypeFunction& function, std::vector<TypeId> typeArguments, std::vector<TypePackId> packArguments)
: function{&function} : function{&function}
, typeArguments(typeArguments) , typeArguments(std::move(typeArguments))
, packArguments(packArguments) , packArguments(std::move(packArguments))
{ {
} }
TypeFunctionInstanceType(NotNull<const TypeFunction> function, std::vector<TypeId> typeArguments, std::vector<TypePackId> packArguments) TypeFunctionInstanceType(NotNull<const TypeFunction> function, std::vector<TypeId> typeArguments, std::vector<TypePackId> packArguments)
: function{function} : function{function}
, typeArguments(typeArguments) , typeArguments(std::move(typeArguments))
, packArguments(packArguments) , packArguments(std::move(packArguments))
{ {
} }
}; };
@ -713,7 +712,7 @@ struct LazyType
{ {
LazyType() = default; LazyType() = default;
LazyType(std::function<void(LazyType&)> unwrap) LazyType(std::function<void(LazyType&)> unwrap)
: unwrap(unwrap) : unwrap(std::move(unwrap))
{ {
} }
@ -800,8 +799,8 @@ struct Type final
{ {
} }
Type(const TypeVariant& ty, bool persistent) Type(TypeVariant ty, bool persistent)
: ty(ty) : ty(std::move(ty))
, persistent(persistent) , persistent(persistent)
{ {
} }

View file

@ -8,6 +8,8 @@
#include <vector> #include <vector>
LUAU_FASTFLAG(LuauTrackTypeAllocations)
namespace Luau namespace Luau
{ {
struct Module; struct Module;
@ -20,6 +22,11 @@ struct TypeArena
// Owning module, if any // Owning module, if any
Module* owningModule = nullptr; Module* owningModule = nullptr;
bool collectSingletonStats = false;
size_t boolSingletonsMinted = 0;
size_t strSingletonsMinted = 0;
DenseHashSet<std::optional<std::string>> uniqueStrSingletonsMinted{std::nullopt};
void clear(); void clear();
template<typename T> template<typename T>
@ -28,6 +35,12 @@ struct TypeArena
if constexpr (std::is_same_v<T, UnionType>) if constexpr (std::is_same_v<T, UnionType>)
LUAU_ASSERT(tv.options.size() >= 2); LUAU_ASSERT(tv.options.size() >= 2);
if constexpr (std::is_same_v<T, SingletonType>)
{
if (FFlag::LuauTrackTypeAllocations && collectSingletonStats)
recordSingletonStats(NotNull{&tv});
}
return addTV(Type(std::move(tv))); return addTV(Type(std::move(tv)));
} }
@ -54,6 +67,8 @@ struct TypeArena
TypeId addTypeFunction(const TypeFunction& function, std::vector<TypeId> typeArguments, std::vector<TypePackId> packArguments = {}); TypeId addTypeFunction(const TypeFunction& function, std::vector<TypeId> typeArguments, std::vector<TypePackId> packArguments = {});
TypePackId addTypePackFunction(const TypePackFunction& function, std::initializer_list<TypeId> types); TypePackId addTypePackFunction(const TypePackFunction& function, std::initializer_list<TypeId> types);
TypePackId addTypePackFunction(const TypePackFunction& function, std::vector<TypeId> typeArguments, std::vector<TypePackId> packArguments = {}); TypePackId addTypePackFunction(const TypePackFunction& function, std::vector<TypeId> typeArguments, std::vector<TypePackId> packArguments = {});
void recordSingletonStats(NotNull<SingletonType> singleton);
}; };
void freeze(TypeArena& arena); void freeze(TypeArena& arena);

View file

@ -318,6 +318,32 @@ std::optional<TypeId> extractMatchingTableType(std::vector<TypeId>& tables, Type
*/ */
bool isRecord(const AstExprTable::Item& item); bool isRecord(const AstExprTable::Item& item);
/**
* Do a quick check for whether the type `ty` is exactly `false | nil`. This
* will *not* do any sort of semantic analysis, for example the type:
*
* (boolean?) & (false | nil)
*
* ... will not be considered falsy, despite it being semantically equivalent
* to `false | nil`.
*
* @return Whether the input is approximately `false | nil`.
*/
bool isApproximatelyFalsyType(TypeId ty);
/**
* Do a quick check for whether the type `ty` is exactly `~(false | nil)`.
* This will *not* do any sort of semantic analysis, for example the type:
*
* unknown & ~(false | nil)
*
* ... will not be considered falsy, despite it being semantically equivalent
* to `~(false | nil)`.
*
* @return Whether the input is approximately `~(false | nil)`.
*/
bool isApproximatelyTruthyType(TypeId ty);
// Unwraps any grouping expressions iteratively. // Unwraps any grouping expressions iteratively.
AstExpr* unwrapGroup(AstExpr* expr); AstExpr* unwrapGroup(AstExpr* expr);

View file

@ -294,7 +294,7 @@ struct GenericTypeVisitor
traverse(*ty); traverse(*ty);
} }
else else
traverse(prop.type()); traverse(prop.type_DEPRECATED());
} }
if (ttv->indexer) if (ttv->indexer)
@ -332,7 +332,7 @@ struct GenericTypeVisitor
traverse(*ty); traverse(*ty);
} }
else else
traverse(prop.type()); traverse(prop.type_DEPRECATED());
} }
if (etv->parent) if (etv->parent)

View file

@ -8,8 +8,6 @@
#include <math.h> #include <math.h>
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
namespace Luau namespace Luau
{ {
@ -433,16 +431,8 @@ struct AstJsonEncoder : public AstVisitor
if (node->self) if (node->self)
PROP(self); PROP(self);
PROP(args); PROP(args);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
if (node->returnAnnotation) if (node->returnAnnotation)
PROP(returnAnnotation); PROP(returnAnnotation);
}
else
{
if (node->returnAnnotation_DEPRECATED)
write("returnAnnotation", node->returnAnnotation_DEPRECATED);
}
PROP(vararg); PROP(vararg);
PROP(varargLocation); PROP(varargLocation);
if (node->varargAnnotation) if (node->varargAnnotation)
@ -912,10 +902,7 @@ struct AstJsonEncoder : public AstVisitor
PROP(paramNames); PROP(paramNames);
PROP(vararg); PROP(vararg);
PROP(varargLocation); PROP(varargLocation);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
PROP(retTypes); PROP(retTypes);
else
write("retTypes", node->retTypes_DEPRECATED);
PROP(generics); PROP(generics);
PROP(genericPacks); PROP(genericPacks);
} }
@ -1061,10 +1048,7 @@ struct AstJsonEncoder : public AstVisitor
PROP(genericPacks); PROP(genericPacks);
PROP(argTypes); PROP(argTypes);
PROP(argNames); PROP(argNames);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
PROP(returnTypes); PROP(returnTypes);
else
write("returnTypes", node->returnTypes_DEPRECATED);
} }
); );
} }

View file

@ -12,6 +12,7 @@
#include <algorithm> #include <algorithm>
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps)
namespace Luau namespace Luau
{ {
@ -524,7 +525,18 @@ static std::optional<DocumentationSymbol> getMetatableDocumentation(
if (indexIt == mtable->props.end()) if (indexIt == mtable->props.end())
return std::nullopt; return std::nullopt;
TypeId followed = follow(indexIt->second.type()); TypeId followed;
if (FFlag::LuauSolverV2 && FFlag::LuauRemoveTypeCallsForReadWriteProps)
{
if (indexIt->second.readTy)
followed = follow(*indexIt->second.readTy);
else if (indexIt->second.writeTy)
followed = follow(*indexIt->second.writeTy);
else
return std::nullopt;
}
else
followed = follow(indexIt->second.type_DEPRECATED());
const TableType* ttv = get<TableType>(followed); const TableType* ttv = get<TableType>(followed);
if (!ttv) if (!ttv)
return std::nullopt; return std::nullopt;
@ -539,7 +551,7 @@ static std::optional<DocumentationSymbol> getMetatableDocumentation(
return checkOverloadedDocumentationSymbol(module, *ty, parentExpr, propIt->second.documentationSymbol); return checkOverloadedDocumentationSymbol(module, *ty, parentExpr, propIt->second.documentationSymbol);
} }
else else
return checkOverloadedDocumentationSymbol(module, propIt->second.type(), parentExpr, propIt->second.documentationSymbol); return checkOverloadedDocumentationSymbol(module, propIt->second.type_DEPRECATED(), parentExpr, propIt->second.documentationSymbol);
return std::nullopt; return std::nullopt;
} }
@ -571,7 +583,7 @@ std::optional<DocumentationSymbol> getDocumentationSymbolAtPosition(const Source
return checkOverloadedDocumentationSymbol(module, *ty, parentExpr, propIt->second.documentationSymbol); return checkOverloadedDocumentationSymbol(module, *ty, parentExpr, propIt->second.documentationSymbol);
} }
else else
return checkOverloadedDocumentationSymbol(module, propIt->second.type(), parentExpr, propIt->second.documentationSymbol); return checkOverloadedDocumentationSymbol(module, propIt->second.type_DEPRECATED(), parentExpr, propIt->second.documentationSymbol);
} }
} }
else if (const ExternType* etv = get<ExternType>(parentTy)) else if (const ExternType* etv = get<ExternType>(parentTy))
@ -587,7 +599,7 @@ std::optional<DocumentationSymbol> getDocumentationSymbolAtPosition(const Source
} }
else else
return checkOverloadedDocumentationSymbol( return checkOverloadedDocumentationSymbol(
module, propIt->second.type(), parentExpr, propIt->second.documentationSymbol module, propIt->second.type_DEPRECATED(), parentExpr, propIt->second.documentationSymbol
); );
} }
etv = etv->parent ? Luau::get<Luau::ExternType>(*etv->parent) : nullptr; etv = etv->parent ? Luau::get<Luau::ExternType>(*etv->parent) : nullptr;

View file

@ -21,11 +21,12 @@
#include <utility> #include <utility>
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps)
LUAU_FASTINT(LuauTypeInferIterationLimit) LUAU_FASTINT(LuauTypeInferIterationLimit)
LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAGVARIABLE(DebugLuauMagicVariableNames) LUAU_FASTFLAGVARIABLE(DebugLuauMagicVariableNames)
LUAU_FASTFLAGVARIABLE(LuauAutocompleteMissingFollows) LUAU_FASTFLAGVARIABLE(LuauAutocompleteMissingFollows)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) LUAU_FASTFLAG(LuauImplicitTableIndexerKeys2)
static const std::unordered_set<std::string> kStatementStartingKeywords = static const std::unordered_set<std::string> kStatementStartingKeywords =
{"while", "if", "local", "repeat", "function", "do", "for", "return", "break", "continue", "type", "export"}; {"while", "if", "local", "repeat", "function", "do", "for", "return", "break", "continue", "type", "export"};
@ -336,7 +337,7 @@ static void autocompleteProps(
continue; continue;
} }
else else
type = follow(prop.type()); type = follow(prop.type_DEPRECATED());
TypeCorrectKind typeCorrect = indexType == PropIndexType::Key TypeCorrectKind typeCorrect = indexType == PropIndexType::Key
? TypeCorrectKind::Correct ? TypeCorrectKind::Correct
@ -368,7 +369,11 @@ static void autocompleteProps(
auto indexIt = mtable->props.find("__index"); auto indexIt = mtable->props.find("__index");
if (indexIt != mtable->props.end()) if (indexIt != mtable->props.end())
{ {
TypeId followed = follow(indexIt->second.type()); TypeId followed;
if (FFlag::LuauRemoveTypeCallsForReadWriteProps && FFlag::LuauSolverV2)
followed = follow(*indexIt->second.readTy);
else
followed = follow(indexIt->second.type_DEPRECATED());
if (get<TableType>(followed) || get<MetatableType>(followed)) if (get<TableType>(followed) || get<MetatableType>(followed))
{ {
autocompleteProps(module, typeArena, builtinTypes, rootTy, followed, indexType, nodes, result, seen); autocompleteProps(module, typeArena, builtinTypes, rootTy, followed, indexType, nodes, result, seen);
@ -652,7 +657,6 @@ static std::optional<TypeId> findTypeElementAt(const AstTypeList& astTypeList, T
static std::optional<TypeId> findTypeElementAt(AstTypePack* astTypePack, TypePackId tp, Position position) static std::optional<TypeId> findTypeElementAt(AstTypePack* astTypePack, TypePackId tp, Position position)
{ {
LUAU_ASSERT(FFlag::LuauStoreReturnTypesAsPackOnAst);
if (const auto typePack = astTypePack->as<AstTypePackExplicit>()) if (const auto typePack = astTypePack->as<AstTypePackExplicit>())
{ {
return findTypeElementAt(typePack->typeList, tp, position); return findTypeElementAt(typePack->typeList, tp, position);
@ -694,17 +698,9 @@ static std::optional<TypeId> findTypeElementAt(AstType* astType, TypeId ty, Posi
if (auto element = findTypeElementAt(type->argTypes, ftv->argTypes, position)) if (auto element = findTypeElementAt(type->argTypes, ftv->argTypes, position))
return element; return element;
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
if (auto element = findTypeElementAt(type->returnTypes, ftv->retTypes, position)) if (auto element = findTypeElementAt(type->returnTypes, ftv->retTypes, position))
return element; return element;
} }
else
{
if (auto element = findTypeElementAt(type->returnTypes_DEPRECATED, ftv->retTypes, position))
return element;
}
}
// It's possible to walk through other types like intrsection and unions if we find value in doing that // It's possible to walk through other types like intrsection and unions if we find value in doing that
return {}; return {};
@ -1033,8 +1029,6 @@ AutocompleteEntryMap autocompleteTypeNames(
} }
} }
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
if (!node->returnAnnotation) if (!node->returnAnnotation)
return result; return result;
@ -1084,44 +1078,6 @@ AutocompleteEntryMap autocompleteTypeNames(
} }
} }
} }
else
{
if (!node->returnAnnotation_DEPRECATED)
return result;
for (size_t i = 0; i < node->returnAnnotation_DEPRECATED->types.size; i++)
{
AstType* ret = node->returnAnnotation_DEPRECATED->types.data[i];
if (ret->location.containsClosed(position))
{
if (const FunctionType* ftv = tryGetExpectedFunctionType(module, node))
{
if (auto ty = tryGetTypePackTypeAt(ftv->retTypes, i))
tryAddTypeCorrectSuggestion(result, startScope, topType, *ty, position);
}
// TODO: with additional type information, we could suggest inferred return type here
break;
}
}
if (AstTypePack* retTp = node->returnAnnotation_DEPRECATED->tailType)
{
if (auto variadic = retTp->as<AstTypePackVariadic>())
{
if (variadic->location.containsClosed(position))
{
if (const FunctionType* ftv = tryGetExpectedFunctionType(module, node))
{
if (auto ty = tryGetTypePackTypeAt(ftv->retTypes, ~0u))
tryAddTypeCorrectSuggestion(result, std::move(startScope), topType, *ty, position);
}
}
}
}
}
}
return result; return result;
} }
@ -2074,8 +2030,11 @@ AutocompleteResult autocomplete_(
{ {
AutocompleteEntryMap result; AutocompleteEntryMap result;
if (!FFlag::LuauImplicitTableIndexerKeys2)
{
if (auto it = module->astExpectedTypes.find(node->asExpr())) if (auto it = module->astExpectedTypes.find(node->asExpr()))
autocompleteStringSingleton(*it, false, node, position, result); autocompleteStringSingleton(*it, false, node, position, result);
}
if (ancestry.size() >= 2) if (ancestry.size() >= 2)
{ {
@ -2094,6 +2053,12 @@ AutocompleteResult autocomplete_(
} }
} }
if (FFlag::LuauImplicitTableIndexerKeys2)
{
if (auto it = module->astExpectedTypes.find(node->asExpr()))
autocompleteStringSingleton(*it, false, node, position, result);
}
return {std::move(result), ancestry, AutocompleteContext::String}; return {std::move(result), ancestry, AutocompleteContext::String};
} }
else if (stringPartOfInterpString(node, position)) else if (stringPartOfInterpString(node, position))

View file

@ -31,6 +31,7 @@
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauEagerGeneralization4)
LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps)
LUAU_FASTFLAGVARIABLE(LuauTableCloneClonesType3) LUAU_FASTFLAGVARIABLE(LuauTableCloneClonesType3)
LUAU_FASTFLAGVARIABLE(LuauStringFormatImprovements) LUAU_FASTFLAGVARIABLE(LuauStringFormatImprovements)
LUAU_FASTFLAGVARIABLE(LuauMagicFreezeCheckBlocked2) LUAU_FASTFLAGVARIABLE(LuauMagicFreezeCheckBlocked2)
@ -336,7 +337,14 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
auto it = stringMetatableTable->props.find("__index"); auto it = stringMetatableTable->props.find("__index");
LUAU_ASSERT(it != stringMetatableTable->props.end()); LUAU_ASSERT(it != stringMetatableTable->props.end());
addGlobalBinding(globals, "string", it->second.type(), "@luau");
if (FFlag::LuauRemoveTypeCallsForReadWriteProps)
{
addGlobalBinding(globals, "string", *it->second.readTy, "@luau");
addGlobalBinding(globals, "string", *it->second.writeTy, "@luau");
}
else
addGlobalBinding(globals, "string", it->second.type_DEPRECATED(), "@luau");
// Setup 'vector' metatable // Setup 'vector' metatable
if (auto it = globals.globalScope->exportedTypeBindings.find("vector"); it != globals.globalScope->exportedTypeBindings.end()) if (auto it = globals.globalScope->exportedTypeBindings.find("vector"); it != globals.globalScope->exportedTypeBindings.end())
@ -494,10 +502,20 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
ttv->props["foreach"].deprecated = true; ttv->props["foreach"].deprecated = true;
ttv->props["foreachi"].deprecated = true; ttv->props["foreachi"].deprecated = true;
attachMagicFunction(ttv->props["pack"].type(), std::make_shared<MagicPack>()); if (FFlag::LuauRemoveTypeCallsForReadWriteProps)
{
attachMagicFunction(*ttv->props["pack"].readTy, std::make_shared<MagicPack>());
if (FFlag::LuauTableCloneClonesType3) if (FFlag::LuauTableCloneClonesType3)
attachMagicFunction(ttv->props["clone"].type(), std::make_shared<MagicClone>()); attachMagicFunction(*ttv->props["clone"].readTy, std::make_shared<MagicClone>());
attachMagicFunction(ttv->props["freeze"].type(), std::make_shared<MagicFreeze>()); attachMagicFunction(*ttv->props["freeze"].readTy, std::make_shared<MagicFreeze>());
}
else
{
attachMagicFunction(ttv->props["pack"].type_DEPRECATED(), std::make_shared<MagicPack>());
if (FFlag::LuauTableCloneClonesType3)
attachMagicFunction(ttv->props["clone"].type_DEPRECATED(), std::make_shared<MagicClone>());
attachMagicFunction(ttv->props["freeze"].type_DEPRECATED(), std::make_shared<MagicFreeze>());
}
} }
TypeId requireTy = getGlobalBinding(globals, "require"); TypeId requireTy = getGlobalBinding(globals, "require");
@ -1649,7 +1667,7 @@ std::optional<WithPredicate<TypePackId>> MagicClone::handleOldSolver(
return std::nullopt; return std::nullopt;
CloneState cloneState{typechecker.builtinTypes}; CloneState cloneState{typechecker.builtinTypes};
TypeId resultType = shallowClone(inputType, arena, cloneState); TypeId resultType = shallowClone(inputType, arena, cloneState, /* clonePersistentTypes */ false);
TypePackId clonedTypePack = arena.addTypePack({resultType}); TypePackId clonedTypePack = arena.addTypePack({resultType});
return WithPredicate<TypePackId>{clonedTypePack}; return WithPredicate<TypePackId>{clonedTypePack};

View file

@ -232,7 +232,7 @@ private:
else else
{ {
return Property{ return Property{
shallowClone(p.type()), shallowClone(p.type_DEPRECATED()),
p.deprecated, p.deprecated,
p.deprecatedSuggestion, p.deprecatedSuggestion,
p.location, p.location,
@ -551,9 +551,9 @@ public:
} // namespace } // namespace
TypePackId shallowClone(TypePackId tp, TypeArena& dest, CloneState& cloneState, bool ignorePersistent) TypePackId shallowClone(TypePackId tp, TypeArena& dest, CloneState& cloneState, bool clonePersistentTypes)
{ {
if (tp->persistent && !ignorePersistent) if (tp->persistent && !clonePersistentTypes)
return tp; return tp;
TypeCloner cloner{ TypeCloner cloner{
@ -562,15 +562,15 @@ TypePackId shallowClone(TypePackId tp, TypeArena& dest, CloneState& cloneState,
NotNull{&cloneState.seenTypes}, NotNull{&cloneState.seenTypes},
NotNull{&cloneState.seenTypePacks}, NotNull{&cloneState.seenTypePacks},
nullptr, nullptr,
ignorePersistent ? tp : nullptr clonePersistentTypes ? tp : nullptr
}; };
return cloner.shallowClone(tp); return cloner.shallowClone(tp);
} }
TypeId shallowClone(TypeId typeId, TypeArena& dest, CloneState& cloneState, bool ignorePersistent) TypeId shallowClone(TypeId typeId, TypeArena& dest, CloneState& cloneState, bool clonePersistentTypes)
{ {
if (typeId->persistent && !ignorePersistent) if (typeId->persistent && !clonePersistentTypes)
return typeId; return typeId;
TypeCloner cloner{ TypeCloner cloner{
@ -578,7 +578,7 @@ TypeId shallowClone(TypeId typeId, TypeArena& dest, CloneState& cloneState, bool
cloneState.builtinTypes, cloneState.builtinTypes,
NotNull{&cloneState.seenTypes}, NotNull{&cloneState.seenTypes},
NotNull{&cloneState.seenTypePacks}, NotNull{&cloneState.seenTypePacks},
ignorePersistent ? typeId : nullptr, clonePersistentTypes ? typeId : nullptr,
nullptr nullptr
}; };

View file

@ -36,7 +36,6 @@ LUAU_FASTFLAG(DebugLuauLogSolverToJson)
LUAU_FASTFLAG(DebugLuauMagicTypes) LUAU_FASTFLAG(DebugLuauMagicTypes)
LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauEagerGeneralization4)
LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauEagerGeneralization4)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
LUAU_FASTFLAG(LuauGlobalVariableModuleIsolation) LUAU_FASTFLAG(LuauGlobalVariableModuleIsolation)
LUAU_FASTFLAGVARIABLE(LuauEnableWriteOnlyProperties) LUAU_FASTFLAGVARIABLE(LuauEnableWriteOnlyProperties)
LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions) LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions)
@ -49,8 +48,10 @@ LUAU_FASTFLAGVARIABLE(LuauDisablePrimitiveInferenceInLargeTables)
LUAU_FASTINTVARIABLE(LuauPrimitiveInferenceInTableLimit, 500) LUAU_FASTINTVARIABLE(LuauPrimitiveInferenceInTableLimit, 500)
LUAU_FASTFLAGVARIABLE(LuauUserTypeFunctionAliases) LUAU_FASTFLAGVARIABLE(LuauUserTypeFunctionAliases)
LUAU_FASTFLAGVARIABLE(LuauSkipLvalueForCompoundAssignment) LUAU_FASTFLAGVARIABLE(LuauSkipLvalueForCompoundAssignment)
LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps)
LUAU_FASTFLAGVARIABLE(LuauFollowTypeAlias) LUAU_FASTFLAGVARIABLE(LuauFollowTypeAlias)
LUAU_FASTFLAGVARIABLE(LuauFollowExistingTypeFunction) LUAU_FASTFLAGVARIABLE(LuauFollowExistingTypeFunction)
LUAU_FASTFLAGVARIABLE(LuauRefineTablesWithReadType)
namespace Luau namespace Luau
{ {
@ -206,8 +207,8 @@ ConstraintGenerator::ConstraintGenerator(
NotNull<ModuleResolver> moduleResolver, NotNull<ModuleResolver> moduleResolver,
NotNull<BuiltinTypes> builtinTypes, NotNull<BuiltinTypes> builtinTypes,
NotNull<InternalErrorReporter> ice, NotNull<InternalErrorReporter> ice,
const ScopePtr& globalScope, ScopePtr globalScope,
const ScopePtr& typeFunctionScope, ScopePtr typeFunctionScope,
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope, std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope,
DcrLogger* logger, DcrLogger* logger,
NotNull<DataFlowGraph> dfg, NotNull<DataFlowGraph> dfg,
@ -223,8 +224,8 @@ ConstraintGenerator::ConstraintGenerator(
, typeFunctionRuntime(typeFunctionRuntime) , typeFunctionRuntime(typeFunctionRuntime)
, moduleResolver(moduleResolver) , moduleResolver(moduleResolver)
, ice(ice) , ice(ice)
, globalScope(globalScope) , globalScope(std::move(globalScope))
, typeFunctionScope(typeFunctionScope) , typeFunctionScope(std::move(typeFunctionScope))
, prepareModuleScope(std::move(prepareModuleScope)) , prepareModuleScope(std::move(prepareModuleScope))
, requireCycles(std::move(requireCycles)) , requireCycles(std::move(requireCycles))
, logger(logger) , logger(logger)
@ -607,11 +608,18 @@ void ConstraintGenerator::computeRefinement(
TypeId nextDiscriminantTy = arena->addType(TableType{}); TypeId nextDiscriminantTy = arena->addType(TableType{});
NotNull<TableType> table{getMutable<TableType>(nextDiscriminantTy)}; NotNull<TableType> table{getMutable<TableType>(nextDiscriminantTy)};
if (FFlag::LuauRefineTablesWithReadType)
{
table->props[*key->propName] = Property::readonly(discriminantTy);
}
else
{
// When we fully support read-write properties (i.e. when we allow properties with // When we fully support read-write properties (i.e. when we allow properties with
// completely disparate read and write types), then the following property can be // completely disparate read and write types), then the following property can be
// set to read-only since refinements only tell us about what we read. This cannot // set to read-only since refinements only tell us about what we read. This cannot
// be allowed yet though because it causes read and write types to diverge. // be allowed yet though because it causes read and write types to diverge.
table->props[*key->propName] = Property::rw(discriminantTy); table->props[*key->propName] = Property::rw(discriminantTy);
}
table->scope = scope.get(); table->scope = scope.get();
table->state = TableState::Sealed; table->state = TableState::Sealed;
@ -1998,7 +2006,66 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareExte
else else
{ {
Luau::Property& prop = props[propName]; Luau::Property& prop = props[propName];
TypeId currentTy = prop.type();
if (FFlag::LuauRemoveTypeCallsForReadWriteProps)
{
if (auto readTy = prop.readTy)
{
// We special-case this logic to keep the intersection flat; otherwise we
// would create a ton of nested intersection types.
if (const IntersectionType* itv = get<IntersectionType>(*readTy))
{
std::vector<TypeId> options = itv->parts;
options.push_back(propTy);
TypeId newItv = arena->addType(IntersectionType{std::move(options)});
prop.readTy = newItv;
}
else if (get<FunctionType>(*readTy))
{
TypeId intersection = arena->addType(IntersectionType{{*readTy, propTy}});
prop.readTy = intersection;
}
else
{
reportError(
declaredExternType->location,
GenericError{format("Cannot overload read type of non-function class member '%s'", propName.c_str())}
);
}
}
if (auto writeTy = prop.writeTy)
{
// We special-case this logic to keep the intersection flat; otherwise we
// would create a ton of nested intersection types.
if (const IntersectionType* itv = get<IntersectionType>(*writeTy))
{
std::vector<TypeId> options = itv->parts;
options.push_back(propTy);
TypeId newItv = arena->addType(IntersectionType{std::move(options)});
prop.writeTy = newItv;
}
else if (get<FunctionType>(*writeTy))
{
TypeId intersection = arena->addType(IntersectionType{{*writeTy, propTy}});
prop.writeTy = intersection;
}
else
{
reportError(
declaredExternType->location,
GenericError{format("Cannot overload write type of non-function class member '%s'", propName.c_str())}
);
}
}
}
else
{
TypeId currentTy = prop.type_DEPRECATED();
// We special-case this logic to keep the intersection flat; otherwise we // We special-case this logic to keep the intersection flat; otherwise we
// would create a ton of nested intersection types. // would create a ton of nested intersection types.
@ -2020,7 +2087,10 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareExte
} }
else else
{ {
reportError(declaredExternType->location, GenericError{format("Cannot overload non-function class member '%s'", propName.c_str())}); reportError(
declaredExternType->location, GenericError{format("Cannot overload non-function class member '%s'", propName.c_str())}
);
}
} }
} }
} }
@ -2052,8 +2122,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareFunc
funScope = childScope(global, scope); funScope = childScope(global, scope);
TypePackId paramPack = resolveTypePack(funScope, global->params, /* inTypeArguments */ false); TypePackId paramPack = resolveTypePack(funScope, global->params, /* inTypeArguments */ false);
TypePackId retPack = FFlag::LuauStoreReturnTypesAsPackOnAst ? resolveTypePack(funScope, global->retTypes, /* inTypeArguments */ false) TypePackId retPack = resolveTypePack(funScope, global->retTypes, /* inTypeArguments */ false);
: resolveTypePack(funScope, global->retTypes_DEPRECATED, /* inTypeArguments */ false);
FunctionDefinition defn; FunctionDefinition defn;
@ -3538,7 +3607,7 @@ ConstraintGenerator::FunctionSignature ConstraintGenerator::checkFunctionSignatu
// If there is both an annotation and an expected type, the annotation wins. // If there is both an annotation and an expected type, the annotation wins.
// Type checking will sort out any discrepancies later. // Type checking will sort out any discrepancies later.
if (FFlag::LuauStoreReturnTypesAsPackOnAst && fn->returnAnnotation) if (fn->returnAnnotation)
{ {
TypePackId annotatedRetType = TypePackId annotatedRetType =
resolveTypePack(signatureScope, fn->returnAnnotation, /* inTypeArguments */ false, /* replaceErrorWithFresh*/ true); resolveTypePack(signatureScope, fn->returnAnnotation, /* inTypeArguments */ false, /* replaceErrorWithFresh*/ true);
@ -3548,16 +3617,6 @@ ConstraintGenerator::FunctionSignature ConstraintGenerator::checkFunctionSignatu
LUAU_ASSERT(get<FreeTypePack>(returnType)); LUAU_ASSERT(get<FreeTypePack>(returnType));
emplaceTypePack<BoundTypePack>(asMutable(returnType), annotatedRetType); emplaceTypePack<BoundTypePack>(asMutable(returnType), annotatedRetType);
} }
else if (!FFlag::LuauStoreReturnTypesAsPackOnAst && fn->returnAnnotation_DEPRECATED)
{
TypePackId annotatedRetType =
resolveTypePack(signatureScope, *fn->returnAnnotation_DEPRECATED, /* inTypeArguments */ false, /* replaceErrorWithFresh*/ true);
// We bind the annotated type directly here so that, when we need to
// generate constraints for return types, we have a guarantee that we
// know the annotated return type already, if one was provided.
LUAU_ASSERT(get<FreeTypePack>(returnType));
emplaceTypePack<BoundTypePack>(asMutable(returnType), annotatedRetType);
}
else if (expectedFunction) else if (expectedFunction)
{ {
emplaceTypePack<BoundTypePack>(asMutable(returnType), expectedFunction->retTypes); emplaceTypePack<BoundTypePack>(asMutable(returnType), expectedFunction->retTypes);
@ -3805,16 +3864,7 @@ TypeId ConstraintGenerator::resolveFunctionType(
AstTypePackExplicit tempArgTypes{Location{}, fn->argTypes}; AstTypePackExplicit tempArgTypes{Location{}, fn->argTypes};
TypePackId argTypes = resolveTypePack_(signatureScope, &tempArgTypes, inTypeArguments, replaceErrorWithFresh); TypePackId argTypes = resolveTypePack_(signatureScope, &tempArgTypes, inTypeArguments, replaceErrorWithFresh);
TypePackId returnTypes; TypePackId returnTypes = resolveTypePack_(signatureScope, fn->returnTypes, inTypeArguments, replaceErrorWithFresh);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
returnTypes = resolveTypePack_(signatureScope, fn->returnTypes, inTypeArguments, replaceErrorWithFresh);
}
else
{
AstTypePackExplicit tempRetTypes{Location{}, fn->returnTypes_DEPRECATED};
returnTypes = resolveTypePack_(signatureScope, &tempRetTypes, inTypeArguments, replaceErrorWithFresh);
}
// TODO: FunctionType needs a pointer to the scope so that we know // TODO: FunctionType needs a pointer to the scope so that we know
// how to quantify/instantiate it. // how to quantify/instantiate it.

View file

@ -39,6 +39,8 @@ LUAU_FASTFLAGVARIABLE(LuauInsertErrorTypesIntoIndexerResult)
LUAU_FASTFLAGVARIABLE(LuauClipVariadicAnysFromArgsToGenericFuncs2) LUAU_FASTFLAGVARIABLE(LuauClipVariadicAnysFromArgsToGenericFuncs2)
LUAU_FASTFLAGVARIABLE(LuauAvoidGenericsLeakingDuringFunctionCallCheck) LUAU_FASTFLAGVARIABLE(LuauAvoidGenericsLeakingDuringFunctionCallCheck)
LUAU_FASTFLAGVARIABLE(LuauMissingFollowInAssignIndexConstraint) LUAU_FASTFLAGVARIABLE(LuauMissingFollowInAssignIndexConstraint)
LUAU_FASTFLAGVARIABLE(LuauRemoveTypeCallsForReadWriteProps)
LUAU_FASTFLAGVARIABLE(LuauTableLiteralSubtypeCheckFunctionCalls)
namespace Luau namespace Luau
{ {
@ -1834,15 +1836,26 @@ bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNull<con
} }
} }
else if (expr->is<AstExprConstantBool>() || expr->is<AstExprConstantString>() || expr->is<AstExprConstantNumber>() || else if (expr->is<AstExprConstantBool>() || expr->is<AstExprConstantString>() || expr->is<AstExprConstantNumber>() ||
expr->is<AstExprConstantNil>()) expr->is<AstExprConstantNil>() || (FFlag::LuauTableLiteralSubtypeCheckFunctionCalls && expr->is<AstExprTable>()))
{ {
ReferentialReplacer replacer{arena, NotNull{&replacements}, NotNull{&replacementPacks}}; ReferentialReplacer replacer{arena, NotNull{&replacements}, NotNull{&replacementPacks}};
if (auto res = replacer.substitute(expectedArgTy)) if (auto res = replacer.substitute(expectedArgTy))
{
if (FFlag::LuauTableLiteralSubtypeCheckFunctionCalls)
{
// If we do this replacement and there are type
// functions in the final type, then we need to
// ensure those get reduced.
InstantiationQueuer queuer{constraint->scope, constraint->location, this};
queuer.traverse(*res);
}
u2.unify(actualArgTy, *res); u2.unify(actualArgTy, *res);
}
else else
u2.unify(actualArgTy, expectedArgTy); u2.unify(actualArgTy, expectedArgTy);
} }
else if (expr->is<AstExprTable>() && !ContainsGenerics::hasGeneric(expectedArgTy, NotNull{&genericTypesAndPacks})) else if (!FFlag::LuauTableLiteralSubtypeCheckFunctionCalls && expr->is<AstExprTable>() &&
!ContainsGenerics::hasGeneric(expectedArgTy, NotNull{&genericTypesAndPacks}))
{ {
Subtyping sp{builtinTypes, arena, simplifier, normalizer, typeFunctionRuntime, NotNull{&iceReporter}}; Subtyping sp{builtinTypes, arena, simplifier, normalizer, typeFunctionRuntime, NotNull{&iceReporter}};
std::vector<TypeId> toBlock; std::vector<TypeId> toBlock;
@ -1852,6 +1865,27 @@ bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNull<con
LUAU_ASSERT(toBlock.empty()); LUAU_ASSERT(toBlock.empty());
} }
} }
if (FFlag::LuauTableLiteralSubtypeCheckFunctionCalls)
{
// Consider:
//
// local Direction = { Left = 1, Right = 2 }
// type Direction = keyof<Direction>
//
// local function move(dirs: { Direction }) --[[...]] end
//
// move({ "Left", "Right", "Left", "Right" })
//
// We need `keyof<Direction>` to reduce prior to inferring that the
// arguments to `move` must generalize to their lower bounds. This
// is how we ensure that ordering.
for (auto& c : u2.incompleteSubtypes)
{
NotNull<Constraint> addition = pushConstraint(constraint->scope, constraint->location, std::move(c));
inheritBlocks(constraint, addition);
}
}
} }
else else
{ {
@ -3173,8 +3207,16 @@ TablePropLookupResult ConstraintSolver::lookupTableProp(
return {{}, result.propType}; return {{}, result.propType};
// TODO: __index can be an overloaded function. // TODO: __index can be an overloaded function.
//
TypeId indexType = follow(indexProp->second.type()); if (FFlag::LuauRemoveTypeCallsForReadWriteProps)
{
// if the property is write-only, then surely we cannot read from it.
if (indexProp->second.isWriteOnly())
return {{}, builtinTypes->errorType};
}
TypeId indexType = FFlag::LuauRemoveTypeCallsForReadWriteProps ? follow(*indexProp->second.readTy) : follow(indexProp->second.type_DEPRECATED());
if (auto ft = get<FunctionType>(indexType)) if (auto ft = get<FunctionType>(indexType))
{ {
@ -3212,7 +3254,16 @@ TablePropLookupResult ConstraintSolver::lookupTableProp(
if (indexProp == metatable->props.end()) if (indexProp == metatable->props.end())
return {{}, std::nullopt}; return {{}, std::nullopt};
return lookupTableProp(constraint, indexProp->second.type(), propName, context, inConditional, suppressSimplification, seen); if (FFlag::LuauRemoveTypeCallsForReadWriteProps)
{
// if the property is write-only, then surely we cannot read from it.
if (indexProp->second.isWriteOnly())
return {{}, builtinTypes->errorType};
return lookupTableProp(constraint, *indexProp->second.readTy, propName, context, inConditional, suppressSimplification, seen);
}
else
return lookupTableProp(constraint, indexProp->second.type_DEPRECATED(), propName, context, inConditional, suppressSimplification, seen);
} }
else if (auto ft = get<FreeType>(subjectType)) else if (auto ft = get<FreeType>(subjectType))
{ {

View file

@ -14,7 +14,6 @@
LUAU_FASTFLAG(DebugLuauFreezeArena) LUAU_FASTFLAG(DebugLuauFreezeArena)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAGVARIABLE(LuauDfgScopeStackNotNull) LUAU_FASTFLAGVARIABLE(LuauDfgScopeStackNotNull)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
LUAU_FASTFLAGVARIABLE(LuauDoNotAddUpvalueTypesToLocalType) LUAU_FASTFLAGVARIABLE(LuauDoNotAddUpvalueTypesToLocalType)
LUAU_FASTFLAGVARIABLE(LuauDfgIfBlocksShouldRespectControlFlow) LUAU_FASTFLAGVARIABLE(LuauDfgIfBlocksShouldRespectControlFlow)
LUAU_FASTFLAGVARIABLE(LuauDfgAllowUpdatesInLoops) LUAU_FASTFLAGVARIABLE(LuauDfgAllowUpdatesInLoops)
@ -951,10 +950,7 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatDeclareFunction* d)
visitGenerics(d->generics); visitGenerics(d->generics);
visitGenericPacks(d->genericPacks); visitGenericPacks(d->genericPacks);
visitTypeList(d->params); visitTypeList(d->params);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
visitTypePack(d->retTypes); visitTypePack(d->retTypes);
else
visitTypeList(d->retTypes_DEPRECATED);
return ControlFlow::None; return ControlFlow::None;
} }
@ -1169,16 +1165,8 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprFunction* f)
if (f->varargAnnotation) if (f->varargAnnotation)
visitTypePack(f->varargAnnotation); visitTypePack(f->varargAnnotation);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
if (f->returnAnnotation) if (f->returnAnnotation)
visitTypePack(f->returnAnnotation); visitTypePack(f->returnAnnotation);
}
else
{
if (f->returnAnnotation_DEPRECATED)
visitTypeList(*f->returnAnnotation_DEPRECATED);
}
// TODO: function body can be re-entrant, as in mutations that occurs at the end of the function can also be // TODO: function body can be re-entrant, as in mutations that occurs at the end of the function can also be
// visible to the beginning of the function, so statically speaking, the body of the function has an exit point // visible to the beginning of the function, so statically speaking, the body of the function has an exit point
@ -1420,10 +1408,7 @@ void DataFlowGraphBuilder::visitType(AstTypeFunction* f)
visitGenerics(f->generics); visitGenerics(f->generics);
visitGenericPacks(f->genericPacks); visitGenericPacks(f->genericPacks);
visitTypeList(f->argTypes); visitTypeList(f->argTypes);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
visitTypePack(f->returnTypes); visitTypePack(f->returnTypes);
else
visitTypeList(f->returnTypes_DEPRECATED);
} }
void DataFlowGraphBuilder::visitType(AstTypeTypeof* t) void DataFlowGraphBuilder::visitType(AstTypeTypeof* t)

View file

@ -25,6 +25,7 @@
LUAU_FASTFLAGVARIABLE(DebugLuauLogSimplification) LUAU_FASTFLAGVARIABLE(DebugLuauLogSimplification)
LUAU_FASTFLAGVARIABLE(DebugLuauLogSimplificationToDot) LUAU_FASTFLAGVARIABLE(DebugLuauLogSimplificationToDot)
LUAU_FASTFLAGVARIABLE(DebugLuauExtraEqSatSanityChecks) LUAU_FASTFLAGVARIABLE(DebugLuauExtraEqSatSanityChecks)
LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps)
namespace Luau::EqSatSimplification namespace Luau::EqSatSimplification
{ {
@ -2369,9 +2370,15 @@ void Simplifier::intersectTableProperty(Id id)
newIntersectionParts.push_back(intersectionParts[index]); newIntersectionParts.push_back(intersectionParts[index]);
} }
Id newTableProp = egraph.add(Intersection{ Id newTableProp =
toId(egraph, builtinTypes, mappingIdToClass, stringCache, it->second.type()), FFlag::LuauRemoveTypeCallsForReadWriteProps
toId(egraph, builtinTypes, mappingIdToClass, stringCache, table1Ty->props.begin()->second.type()) ? egraph.add(Intersection{
toId(egraph, builtinTypes, mappingIdToClass, stringCache, *it->second.readTy),
toId(egraph, builtinTypes, mappingIdToClass, stringCache, *table1Ty->props.begin()->second.readTy)
})
: egraph.add(Intersection{
toId(egraph, builtinTypes, mappingIdToClass, stringCache, it->second.type_DEPRECATED()),
toId(egraph, builtinTypes, mappingIdToClass, stringCache, table1Ty->props.begin()->second.type_DEPRECATED())
}); });
newIntersectionParts.push_back(egraph.add(TTable{jId, {stringCache.add(it->first)}, {newTableProp}})); newIntersectionParts.push_back(egraph.add(TTable{jId, {stringCache.add(it->first)}, {newTableProp}}));
@ -2444,7 +2451,10 @@ void Simplifier::unneededTableModification(Id id)
StringId propName = tbl->propNames[i]; StringId propName = tbl->propNames[i];
const Id propType = tbl->propTypes()[i]; const Id propType = tbl->propTypes()[i];
Id importedProp = toId(egraph, builtinTypes, mappingIdToClass, stringCache, tt->props.at(stringCache.asString(propName)).type()); Id importedProp =
FFlag::LuauRemoveTypeCallsForReadWriteProps
? toId(egraph, builtinTypes, mappingIdToClass, stringCache, *tt->props.at(stringCache.asString(propName)).readTy)
: toId(egraph, builtinTypes, mappingIdToClass, stringCache, tt->props.at(stringCache.asString(propName)).type_DEPRECATED());
if (find(importedProp) != find(propType)) if (find(importedProp) != find(propType))
{ {

View file

@ -20,6 +20,7 @@
LUAU_FASTINTVARIABLE(LuauIndentTypeMismatchMaxTypeLength, 10) LUAU_FASTINTVARIABLE(LuauIndentTypeMismatchMaxTypeLength, 10)
LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauEagerGeneralization4)
LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps)
LUAU_FASTFLAGVARIABLE(LuauBetterCannotCallFunctionPrimitive) LUAU_FASTFLAGVARIABLE(LuauBetterCannotCallFunctionPrimitive)
static std::string wrongNumberOfArgsString( static std::string wrongNumberOfArgsString(
@ -418,7 +419,12 @@ struct ErrorConverter
auto it = mtt->props.find("__call"); auto it = mtt->props.find("__call");
if (it != mtt->props.end()) if (it != mtt->props.end())
return it->second.type(); {
if (FFlag::LuauSolverV2 && FFlag::LuauRemoveTypeCallsForReadWriteProps)
return it->second.readTy;
else
return it->second.type_DEPRECATED();
}
else else
return std::nullopt; return std::nullopt;
} }

View file

@ -8,7 +8,7 @@
#include "Luau/TypeUtils.h" #include "Luau/TypeUtils.h"
#include "Luau/VisitType.h" #include "Luau/VisitType.h"
LUAU_FASTFLAGVARIABLE(LuauImplicitTableIndexerKeys) LUAU_FASTFLAGVARIABLE(LuauImplicitTableIndexerKeys2)
namespace Luau namespace Luau
{ {
@ -147,7 +147,7 @@ struct IndexCollector : public TypeOnceVisitor
bool ExpectedTypeVisitor::visit(AstExprIndexExpr* expr) bool ExpectedTypeVisitor::visit(AstExprIndexExpr* expr)
{ {
if (!FFlag::LuauImplicitTableIndexerKeys) if (!FFlag::LuauImplicitTableIndexerKeys2)
return true; return true;
if (auto ty = astTypes->find(expr->expr)) if (auto ty = astTypes->find(expr->expr))

View file

@ -34,7 +34,6 @@ LUAU_FASTFLAGVARIABLE(LuauBetterScopeSelection)
LUAU_FASTFLAGVARIABLE(LuauBlockDiffFragmentSelection) LUAU_FASTFLAGVARIABLE(LuauBlockDiffFragmentSelection)
LUAU_FASTFLAGVARIABLE(LuauFragmentAcMemoryLeak) LUAU_FASTFLAGVARIABLE(LuauFragmentAcMemoryLeak)
LUAU_FASTFLAGVARIABLE(LuauGlobalVariableModuleIsolation) LUAU_FASTFLAGVARIABLE(LuauGlobalVariableModuleIsolation)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
LUAU_FASTFLAGVARIABLE(LuauFragmentAutocompleteIfRecommendations) LUAU_FASTFLAGVARIABLE(LuauFragmentAutocompleteIfRecommendations)
LUAU_FASTFLAG(LuauExpectedTypeVisitor) LUAU_FASTFLAG(LuauExpectedTypeVisitor)
LUAU_FASTFLAGVARIABLE(LuauPopulateRefinedTypesInFragmentFromOldSolver) LUAU_FASTFLAGVARIABLE(LuauPopulateRefinedTypesInFragmentFromOldSolver)
@ -51,17 +50,10 @@ Location getFunctionDeclarationExtents(AstExprFunction* exprFn, AstExpr* exprNam
{ {
auto fnBegin = exprFn->location.begin; auto fnBegin = exprFn->location.begin;
auto fnEnd = exprFn->location.end; auto fnEnd = exprFn->location.end;
if (auto returnAnnot = exprFn->returnAnnotation; FFlag::LuauStoreReturnTypesAsPackOnAst && returnAnnot) if (auto returnAnnot = exprFn->returnAnnotation)
{ {
fnEnd = returnAnnot->location.end; fnEnd = returnAnnot->location.end;
} }
else if (auto returnAnnot = exprFn->returnAnnotation_DEPRECATED; !FFlag::LuauStoreReturnTypesAsPackOnAst && returnAnnot)
{
if (returnAnnot->tailType)
fnEnd = returnAnnot->tailType->location.end;
else if (returnAnnot->types.size != 0)
fnEnd = returnAnnot->types.data[returnAnnot->types.size - 1]->location.end;
}
else if (exprFn->args.size != 0) else if (exprFn->args.size != 0)
{ {
auto last = exprFn->args.data[exprFn->args.size - 1]; auto last = exprFn->args.data[exprFn->args.size - 1];
@ -581,7 +573,7 @@ struct UsageFinder : public AstVisitor
bool visit(AstTypePack* node) override bool visit(AstTypePack* node) override
{ {
return FFlag::LuauStoreReturnTypesAsPackOnAst; return true;
} }
bool visit(AstStatTypeAlias* alias) override bool visit(AstStatTypeAlias* alias) override

View file

@ -2,8 +2,8 @@
#include "Luau/Frontend.h" #include "Luau/Frontend.h"
#include "Luau/BuiltinDefinitions.h" #include "Luau/BuiltinDefinitions.h"
#include "Luau/Clone.h"
#include "Luau/Common.h" #include "Luau/Common.h"
#include "Luau/Clone.h"
#include "Luau/Config.h" #include "Luau/Config.h"
#include "Luau/ConstraintGenerator.h" #include "Luau/ConstraintGenerator.h"
#include "Luau/ConstraintSolver.h" #include "Luau/ConstraintSolver.h"
@ -18,8 +18,6 @@
#include "Luau/Scope.h" #include "Luau/Scope.h"
#include "Luau/StringUtils.h" #include "Luau/StringUtils.h"
#include "Luau/TimeTrace.h" #include "Luau/TimeTrace.h"
#include "Luau/ToString.h"
#include "Luau/Transpiler.h"
#include "Luau/TypeArena.h" #include "Luau/TypeArena.h"
#include "Luau/TypeChecker2.h" #include "Luau/TypeChecker2.h"
#include "Luau/TypeInfer.h" #include "Luau/TypeInfer.h"
@ -48,6 +46,8 @@ LUAU_FASTFLAGVARIABLE(DebugLuauForceStrictMode)
LUAU_FASTFLAGVARIABLE(DebugLuauForceNonStrictMode) LUAU_FASTFLAGVARIABLE(DebugLuauForceNonStrictMode)
LUAU_FASTFLAGVARIABLE(LuauNewSolverTypecheckCatchTimeouts) LUAU_FASTFLAGVARIABLE(LuauNewSolverTypecheckCatchTimeouts)
LUAU_FASTFLAGVARIABLE(LuauExpectedTypeVisitor) LUAU_FASTFLAGVARIABLE(LuauExpectedTypeVisitor)
LUAU_FASTFLAGVARIABLE(LuauTrackTypeAllocations)
LUAU_FASTFLAGVARIABLE(LuauUseWorkspacePropToChooseSolver)
namespace Luau namespace Luau
{ {
@ -399,6 +399,21 @@ Frontend::Frontend(FileResolver* fileResolver, ConfigResolver* configResolver, c
{ {
} }
void Frontend::setLuauSolverSelectionFromWorkspace(bool newSolverEnabled)
{
useNewLuauSolver.store(newSolverEnabled);
}
bool Frontend::getLuauSolverSelection() const
{
return useNewLuauSolver.load();
}
bool Frontend::getLuauSolverSelectionFlagged() const
{
return FFlag::LuauUseWorkspacePropToChooseSolver ? getLuauSolverSelection() : FFlag::LuauSolverV2;
}
void Frontend::parse(const ModuleName& name) void Frontend::parse(const ModuleName& name)
{ {
LUAU_TIMETRACE_SCOPE("Frontend::parse", "Frontend"); LUAU_TIMETRACE_SCOPE("Frontend::parse", "Frontend");
@ -983,6 +998,15 @@ void Frontend::checkBuildQueueItem(BuildQueueItem& item)
item.stats.timeCheck += duration; item.stats.timeCheck += duration;
item.stats.filesStrict += 1; item.stats.filesStrict += 1;
if (FFlag::LuauTrackTypeAllocations && item.options.collectTypeAllocationStats)
{
item.stats.typesAllocated += moduleForAutocomplete->internalTypes.types.size();
item.stats.typePacksAllocated += moduleForAutocomplete->internalTypes.typePacks.size();
item.stats.boolSingletonsMinted += moduleForAutocomplete->internalTypes.boolSingletonsMinted;
item.stats.strSingletonsMinted += moduleForAutocomplete->internalTypes.strSingletonsMinted;
item.stats.uniqueStrSingletonsMinted += moduleForAutocomplete->internalTypes.uniqueStrSingletonsMinted.size();
}
if (item.options.customModuleCheck) if (item.options.customModuleCheck)
item.options.customModuleCheck(sourceModule, *moduleForAutocomplete); item.options.customModuleCheck(sourceModule, *moduleForAutocomplete);
@ -1003,6 +1027,15 @@ void Frontend::checkBuildQueueItem(BuildQueueItem& item)
item.stats.filesStrict += mode == Mode::Strict; item.stats.filesStrict += mode == Mode::Strict;
item.stats.filesNonstrict += mode == Mode::Nonstrict; item.stats.filesNonstrict += mode == Mode::Nonstrict;
if (FFlag::LuauTrackTypeAllocations && item.options.collectTypeAllocationStats)
{
item.stats.typesAllocated += module->internalTypes.types.size();
item.stats.typePacksAllocated += module->internalTypes.typePacks.size();
item.stats.boolSingletonsMinted += module->internalTypes.boolSingletonsMinted;
item.stats.strSingletonsMinted += module->internalTypes.strSingletonsMinted;
item.stats.uniqueStrSingletonsMinted += module->internalTypes.uniqueStrSingletonsMinted.size();
}
if (item.options.customModuleCheck) if (item.options.customModuleCheck)
item.options.customModuleCheck(sourceModule, *module); item.options.customModuleCheck(sourceModule, *module);
@ -1124,6 +1157,16 @@ void Frontend::recordItemResult(const BuildQueueItem& item)
stats.filesStrict += item.stats.filesStrict; stats.filesStrict += item.stats.filesStrict;
stats.filesNonstrict += item.stats.filesNonstrict; stats.filesNonstrict += item.stats.filesNonstrict;
if (FFlag::LuauTrackTypeAllocations && item.options.collectTypeAllocationStats)
{
stats.typesAllocated += item.stats.typesAllocated;
stats.typePacksAllocated += item.stats.typePacksAllocated;
stats.boolSingletonsMinted += item.stats.boolSingletonsMinted;
stats.strSingletonsMinted += item.stats.strSingletonsMinted;
stats.uniqueStrSingletonsMinted += item.stats.uniqueStrSingletonsMinted;
}
} }
void Frontend::performQueueItemTask(std::shared_ptr<BuildQueueWorkState> state, size_t itemPos) void Frontend::performQueueItemTask(std::shared_ptr<BuildQueueWorkState> state, size_t itemPos)
@ -1393,6 +1436,8 @@ ModulePtr check(
result->mode = mode; result->mode = mode;
result->internalTypes.owningModule = result.get(); result->internalTypes.owningModule = result.get();
result->interfaceTypes.owningModule = result.get(); result->interfaceTypes.owningModule = result.get();
if (FFlag::LuauTrackTypeAllocations)
result->internalTypes.collectSingletonStats = options.collectTypeAllocationStats;
result->allocator = sourceModule.allocator; result->allocator = sourceModule.allocator;
result->names = sourceModule.names; result->names = sourceModule.names;
result->root = sourceModule.root; result->root = sourceModule.root;

View file

@ -15,6 +15,7 @@
#include "Luau/VisitType.h" #include "Luau/VisitType.h"
LUAU_FASTFLAG(LuauEnableWriteOnlyProperties) LUAU_FASTFLAG(LuauEnableWriteOnlyProperties)
LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps)
LUAU_FASTFLAGVARIABLE(LuauEagerGeneralization4) LUAU_FASTFLAGVARIABLE(LuauEagerGeneralization4)
LUAU_FASTFLAGVARIABLE(LuauGeneralizationCannotMutateAcrossModules) LUAU_FASTFLAGVARIABLE(LuauGeneralizationCannotMutateAcrossModules)
@ -562,7 +563,10 @@ struct FreeTypeSearcher : TypeVisitor
{ {
Polarity p = polarity; Polarity p = polarity;
polarity = Polarity::Mixed; polarity = Polarity::Mixed;
traverse(prop.type()); if (FFlag::LuauRemoveTypeCallsForReadWriteProps)
traverse(*prop.readTy);
else
traverse(prop.type_DEPRECATED());
polarity = p; polarity = p;
} }
else else
@ -586,7 +590,10 @@ struct FreeTypeSearcher : TypeVisitor
Polarity p = polarity; Polarity p = polarity;
polarity = Polarity::Mixed; polarity = Polarity::Mixed;
traverse(prop.type()); if (FFlag::LuauRemoveTypeCallsForReadWriteProps)
traverse(*prop.readTy);
else
traverse(prop.type_DEPRECATED());
polarity = p; polarity = p;
} }
} }
@ -1518,7 +1525,10 @@ struct GenericCounter : TypeVisitor
{ {
Polarity p = polarity; Polarity p = polarity;
polarity = Polarity::Mixed; polarity = Polarity::Mixed;
traverse(prop.type()); if (FFlag::LuauRemoveTypeCallsForReadWriteProps)
traverse(*prop.readTy);
else
traverse(prop.type_DEPRECATED());
polarity = p; polarity = p;
} }
else else
@ -1542,7 +1552,10 @@ struct GenericCounter : TypeVisitor
Polarity p = polarity; Polarity p = polarity;
polarity = Polarity::Mixed; polarity = Polarity::Mixed;
traverse(prop.type()); if (FFlag::LuauRemoveTypeCallsForReadWriteProps)
traverse(*prop.readTy);
else
traverse(prop.type_DEPRECATED());
polarity = p; polarity = p;
} }
} }

View file

@ -6,6 +6,7 @@
#include "Luau/VisitType.h" #include "Luau/VisitType.h"
LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauEagerGeneralization4)
LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps)
LUAU_FASTFLAGVARIABLE(LuauInferPolarityOfReadWriteProperties) LUAU_FASTFLAGVARIABLE(LuauInferPolarityOfReadWriteProperties)
namespace Luau namespace Luau
@ -49,7 +50,7 @@ struct InferPolarity : TypeVisitor
return false; return false;
const Polarity p = polarity; const Polarity p = polarity;
if (FFlag::LuauInferPolarityOfReadWriteProperties) if (FFlag::LuauInferPolarityOfReadWriteProperties || FFlag::LuauRemoveTypeCallsForReadWriteProps)
{ {
for (const auto& [name, prop] : tt.props) for (const auto& [name, prop] : tt.props)
{ {
@ -80,7 +81,7 @@ struct InferPolarity : TypeVisitor
if (prop.isShared()) if (prop.isShared())
{ {
polarity = Polarity::Mixed; polarity = Polarity::Mixed;
traverse(prop.type()); traverse(prop.type_DEPRECATED());
} }
else if (prop.isReadOnly()) else if (prop.isReadOnly())
{ {

View file

@ -16,8 +16,6 @@ LUAU_FASTINTVARIABLE(LuauSuggestionDistance, 4)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
namespace Luau namespace Luau
{ {
@ -907,7 +905,7 @@ private:
bool visit(AstTypePack* node) override bool visit(AstTypePack* node) override
{ {
return FFlag::LuauStoreReturnTypesAsPackOnAst; return true;
} }
bool visit(AstTypeReference* node) override bool visit(AstTypeReference* node) override
@ -1974,7 +1972,7 @@ private:
bool visit(AstTypePack* node) override bool visit(AstTypePack* node) override
{ {
return FFlag::LuauStoreReturnTypesAsPackOnAst; return true;
} }
bool visit(AstTypeTable* node) override bool visit(AstTypeTable* node) override

View file

@ -23,7 +23,6 @@
LUAU_FASTFLAG(DebugLuauMagicTypes) LUAU_FASTFLAG(DebugLuauMagicTypes)
LUAU_FASTFLAGVARIABLE(LuauNewNonStrictVisitTypes2) LUAU_FASTFLAGVARIABLE(LuauNewNonStrictVisitTypes2)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
LUAU_FASTFLAGVARIABLE(LuauNewNonStrictFixGenericTypePacks) LUAU_FASTFLAGVARIABLE(LuauNewNonStrictFixGenericTypePacks)
namespace Luau namespace Luau
@ -773,13 +772,7 @@ struct NonStrictTypeChecker
{ {
visitGenerics(exprFn->generics, exprFn->genericPacks); visitGenerics(exprFn->generics, exprFn->genericPacks);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
visit(exprFn->returnAnnotation); visit(exprFn->returnAnnotation);
else
{
if (exprFn->returnAnnotation_DEPRECATED)
visit(*exprFn->returnAnnotation_DEPRECATED);
}
if (exprFn->varargAnnotation) if (exprFn->varargAnnotation)
visit(exprFn->varargAnnotation); visit(exprFn->varargAnnotation);

View file

@ -23,6 +23,7 @@ LUAU_FASTINTVARIABLE(LuauNormalizeUnionLimit, 100)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAGVARIABLE(LuauNormalizationIntersectTablesPreservesExternTypes) LUAU_FASTFLAGVARIABLE(LuauNormalizationIntersectTablesPreservesExternTypes)
LUAU_FASTFLAGVARIABLE(LuauNormalizationReorderFreeTypeIntersect) LUAU_FASTFLAGVARIABLE(LuauNormalizationReorderFreeTypeIntersect)
LUAU_FASTFLAG(LuauRefineTablesWithReadType)
namespace Luau namespace Luau
{ {
@ -404,7 +405,7 @@ NormalizationResult Normalizer::isInhabited(TypeId ty, Set<TypeId>& seen)
} }
else else
{ {
NormalizationResult res = isInhabited(prop.type(), seen); NormalizationResult res = isInhabited(prop.type_DEPRECATED(), seen);
if (res != NormalizationResult::True) if (res != NormalizationResult::True)
return res; return res;
} }
@ -2513,9 +2514,9 @@ std::optional<TypeId> Normalizer::intersectionOfTables(TypeId here, TypeId there
} }
else else
{ {
prop.setType(intersectionType(hprop.type(), tprop.type())); prop.setType(intersectionType(hprop.type_DEPRECATED(), tprop.type_DEPRECATED()));
hereSubThere &= (prop.type() == hprop.type()); hereSubThere &= (prop.type_DEPRECATED() == hprop.type_DEPRECATED());
thereSubHere &= (prop.type() == tprop.type()); thereSubHere &= (prop.type_DEPRECATED() == tprop.type_DEPRECATED());
} }
} }
@ -3327,6 +3328,7 @@ TypeId Normalizer::typeFromNormal(const NormalizedType& norm)
result.reserve(result.size() + norm.tables.size()); result.reserve(result.size() + norm.tables.size());
for (auto table : norm.tables) for (auto table : norm.tables)
{ {
if (!FFlag::LuauRefineTablesWithReadType)
makeTableShared(table); makeTableShared(table);
result.push_back(table); result.push_back(table);
} }

View file

@ -4,8 +4,6 @@
#include "Luau/Ast.h" #include "Luau/Ast.h"
#include "Luau/Module.h" #include "Luau/Module.h"
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
namespace Luau namespace Luau
{ {
@ -70,7 +68,7 @@ struct RequireTracer : AstVisitor
bool visit(AstTypePack* node) override bool visit(AstTypePack* node) override
{ {
// allow resolving require inside `typeof` annotations // allow resolving require inside `typeof` annotations
return FFlag::LuauStoreReturnTypesAsPackOnAst; return true;
} }
AstExpr* getDependent_DEPRECATED(AstExpr* node) AstExpr* getDependent_DEPRECATED(AstExpr* node)

View file

@ -2,12 +2,14 @@
#include "Luau/Simplify.h" #include "Luau/Simplify.h"
#include "Luau/Clone.h"
#include "Luau/Common.h" #include "Luau/Common.h"
#include "Luau/DenseHash.h" #include "Luau/DenseHash.h"
#include "Luau/RecursionCounter.h" #include "Luau/RecursionCounter.h"
#include "Luau/Set.h" #include "Luau/Set.h"
#include "Luau/Type.h" #include "Luau/Type.h"
#include "Luau/TypeArena.h" #include "Luau/TypeArena.h"
#include "Luau/TypeIds.h"
#include "Luau/TypePairHash.h" #include "Luau/TypePairHash.h"
#include "Luau/TypeUtils.h" #include "Luau/TypeUtils.h"
@ -17,6 +19,9 @@ LUAU_FASTINT(LuauTypeReductionRecursionLimit)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_DYNAMIC_FASTINTVARIABLE(LuauSimplificationComplexityLimit, 8) LUAU_DYNAMIC_FASTINTVARIABLE(LuauSimplificationComplexityLimit, 8)
LUAU_FASTFLAGVARIABLE(LuauSimplificationTableExternType) LUAU_FASTFLAGVARIABLE(LuauSimplificationTableExternType)
LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps)
LUAU_FASTFLAGVARIABLE(LuauRelateTablesAreNeverDisjoint)
LUAU_FASTFLAG(LuauRefineTablesWithReadType)
namespace Luau namespace Luau
{ {
@ -37,10 +42,13 @@ struct TypeSimplifier
TypeId intersectFromParts(std::set<TypeId> parts); TypeId intersectFromParts(std::set<TypeId> parts);
TypeId intersectUnionWithType(TypeId left, TypeId right); TypeId intersectUnionWithType(TypeId left, TypeId right);
TypeId intersectUnions(TypeId left, TypeId right); TypeId intersectUnions(TypeId left, TypeId right);
TypeId intersectNegatedUnion(TypeId left, TypeId right); TypeId intersectNegatedUnion(TypeId left, TypeId right);
TypeId intersectTypeWithNegation(TypeId left, TypeId right); TypeId intersectTypeWithNegation(TypeId left, TypeId right);
TypeId intersectNegations(TypeId left, TypeId right); TypeId intersectNegations(TypeId left, TypeId right);
TypeId intersectIntersectionWithType(TypeId left, TypeId right); TypeId intersectIntersectionWithType(TypeId left, TypeId right);
@ -48,18 +56,32 @@ struct TypeSimplifier
// Attempt to intersect the two types. Does not recurse. Does not handle // Attempt to intersect the two types. Does not recurse. Does not handle
// unions, intersections, or negations. // unions, intersections, or negations.
std::optional<TypeId> basicIntersect(TypeId left, TypeId right); std::optional<TypeId> basicIntersect(TypeId left, TypeId right);
std::optional<TypeId> basicIntersectWithTruthy(TypeId target) const; std::optional<TypeId> basicIntersectWithTruthy(TypeId target) const;
std::optional<TypeId> basicIntersectWithFalsy(TypeId target) const; std::optional<TypeId> basicIntersectWithFalsy(TypeId target) const;
TypeId intersect(TypeId left, TypeId right); TypeId intersect(TypeId left, TypeId right);
TypeId union_(TypeId left, TypeId right); TypeId union_(TypeId left, TypeId right);
TypeId simplify(TypeId ty); TypeId simplify(TypeId ty);
TypeId simplify(TypeId ty, DenseHashSet<TypeId>& seen); TypeId simplify(TypeId ty, DenseHashSet<TypeId>& seen);
std::optional<TypeId> intersectOne(TypeId target, TypeId discriminant) const;
std::optional<TypeId> subtractOne(TypeId target, TypeId discriminant) const;
std::optional<Property> intersectProperty(const Property& target, const Property& discriminant, DenseHashSet<TypeId>& seen) const;
std::optional<TypeId> intersectWithSimpleDiscriminant(TypeId target, TypeId discriminant, DenseHashSet<TypeId>& seen) const;
std::optional<TypeId> intersectWithSimpleDiscriminant(TypeId target, TypeId discriminant) const;
}; };
// Match the exact type false|nil // Match the exact type false|nil
static bool isFalsyType(TypeId ty) static bool isFalsyType_DEPRECATED(TypeId ty)
{ {
ty = follow(ty); ty = follow(ty);
const UnionType* ut = get<UnionType>(ty); const UnionType* ut = get<UnionType>(ty);
@ -103,7 +125,7 @@ static bool isFalsyType(TypeId ty)
} }
// Match the exact type ~(false|nil) // Match the exact type ~(false|nil)
bool isTruthyType(TypeId ty) bool isTruthyType_DEPRECATED(TypeId ty)
{ {
ty = follow(ty); ty = follow(ty);
@ -111,7 +133,7 @@ bool isTruthyType(TypeId ty)
if (!nt) if (!nt)
return false; return false;
return isFalsyType(nt->ty); return isFalsyType_DEPRECATED(nt->ty);
} }
Relation flip(Relation rel) Relation flip(Relation rel)
@ -266,7 +288,7 @@ Relation relateTables(TypeId left, TypeId right, SimplifierSeenSet& seen)
); );
if (!foundPropFromLeftInRight && !foundPropFromRightInLeft && leftTable->props.size() >= 1 && rightTable->props.size() >= 1) if (!foundPropFromLeftInRight && !foundPropFromRightInLeft && leftTable->props.size() >= 1 && rightTable->props.size() >= 1)
return Relation::Disjoint; return FFlag::LuauRelateTablesAreNeverDisjoint ? Relation::Intersects : Relation::Disjoint;
const auto [propName, rightProp] = *begin(rightTable->props); const auto [propName, rightProp] = *begin(rightTable->props);
@ -283,7 +305,11 @@ Relation relateTables(TypeId left, TypeId right, SimplifierSeenSet& seen)
if (!leftProp.isShared() || !rightProp.isShared()) if (!leftProp.isShared() || !rightProp.isShared())
return Relation::Intersects; return Relation::Intersects;
Relation r = relate(leftProp.type(), rightProp.type(), seen); Relation r;
if (FFlag::LuauRemoveTypeCallsForReadWriteProps)
r = relate(*leftProp.readTy, *rightProp.readTy, seen);
else
r = relate(leftProp.type_DEPRECATED(), rightProp.type_DEPRECATED(), seen);
if (r == Relation::Coincident && 1 != leftTable->props.size()) if (r == Relation::Coincident && 1 != leftTable->props.size())
{ {
// eg {tag: "cat", prop: string} & {tag: "cat"} // eg {tag: "cat", prop: string} & {tag: "cat"}
@ -570,7 +596,14 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
{ {
if (auto propInExternType = re->props.find(name); propInExternType != re->props.end()) if (auto propInExternType = re->props.find(name); propInExternType != re->props.end())
{ {
Relation propRel = relate(prop.type(), propInExternType->second.type()); Relation propRel;
if (FFlag::LuauRemoveTypeCallsForReadWriteProps)
{
LUAU_ASSERT(prop.readTy && propInExternType->second.readTy);
propRel = relate(*prop.readTy, *propInExternType->second.readTy);
}
else
propRel = relate(prop.type_DEPRECATED(), propInExternType->second.type_DEPRECATED());
if (propRel == Relation::Disjoint) if (propRel == Relation::Disjoint)
return Relation::Disjoint; return Relation::Disjoint;
@ -604,6 +637,15 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
return Relation::Disjoint; return Relation::Disjoint;
} }
if (FFlag::LuauRefineTablesWithReadType && is<TableType>(right))
{
// FIXME: This could be better in that we can say a table only
// intersects with an extern type if they share a property, but
// for now it is within the contract of the function to claim
// the two intersect.
return Relation::Intersects;
}
return Relation::Disjoint; return Relation::Disjoint;
} }
@ -898,6 +940,15 @@ std::optional<TypeId> TypeSimplifier::basicIntersectWithTruthy(TypeId target) co
{ {
target = follow(target); target = follow(target);
if (FFlag::LuauRefineTablesWithReadType)
{
if (isApproximatelyTruthyType(target))
return target;
if (isApproximatelyFalsyType(target))
return builtinTypes->neverType;
}
if (is<UnknownType>(target)) if (is<UnknownType>(target))
return builtinTypes->truthyType; return builtinTypes->truthyType;
@ -934,6 +985,15 @@ std::optional<TypeId> TypeSimplifier::basicIntersectWithFalsy(TypeId target) con
{ {
target = follow(target); target = follow(target);
if (FFlag::LuauRefineTablesWithReadType)
{
if (isApproximatelyTruthyType(target))
return builtinTypes->neverType;
if (isApproximatelyFalsyType(target))
return target;
}
if (is<NeverType, ErrorType>(target)) if (is<NeverType, ErrorType>(target))
return target; return target;
@ -1252,7 +1312,11 @@ std::optional<TypeId> TypeSimplifier::basicIntersect(TypeId left, TypeId right)
auto it = rt->props.find(propName); auto it = rt->props.find(propName);
if (it != rt->props.end() && leftProp.isShared() && it->second.isShared()) if (it != rt->props.end() && leftProp.isShared() && it->second.isShared())
{ {
Relation r = relate(leftProp.type(), it->second.type()); Relation r;
if (FFlag::LuauRemoveTypeCallsForReadWriteProps)
r = relate(*leftProp.readTy, *it->second.readTy);
else
r = relate(leftProp.type_DEPRECATED(), it->second.type_DEPRECATED());
switch (r) switch (r)
{ {
@ -1302,21 +1366,43 @@ std::optional<TypeId> TypeSimplifier::basicIntersect(TypeId left, TypeId right)
return std::nullopt; return std::nullopt;
} }
if (isTruthyType(left)) if (FFlag::LuauRefineTablesWithReadType)
{
if (isApproximatelyTruthyType(left))
if (auto res = basicIntersectWithTruthy(right)) if (auto res = basicIntersectWithTruthy(right))
return res; return res;
if (isTruthyType(right)) if (isApproximatelyTruthyType(right))
if (auto res = basicIntersectWithTruthy(left)) if (auto res = basicIntersectWithTruthy(left))
return res; return res;
if (isFalsyType(left)) if (isApproximatelyFalsyType(left))
if (auto res = basicIntersectWithFalsy(right)) if (auto res = basicIntersectWithFalsy(right))
return res; return res;
if (isFalsyType(right)) if (isApproximatelyFalsyType(right))
if (auto res = basicIntersectWithFalsy(left)) if (auto res = basicIntersectWithFalsy(left))
return res; return res;
}
else
{
if (isTruthyType_DEPRECATED(left))
if (auto res = basicIntersectWithTruthy(right))
return res;
if (isTruthyType_DEPRECATED(right))
if (auto res = basicIntersectWithTruthy(left))
return res;
if (isFalsyType_DEPRECATED(left))
if (auto res = basicIntersectWithFalsy(right))
return res;
if (isFalsyType_DEPRECATED(right))
if (auto res = basicIntersectWithFalsy(left))
return res;
}
Relation relation = relate(left, right); Relation relation = relate(left, right);
if (left == right || Relation::Coincident == relation) if (left == right || Relation::Coincident == relation)
@ -1548,6 +1634,276 @@ TypeId TypeSimplifier::simplify(TypeId ty, DenseHashSet<TypeId>& seen)
return ty; return ty;
} }
namespace
{
bool isSimpleDiscriminant(TypeId ty, DenseHashSet<TypeId>& seen)
{
ty = follow(ty);
// If we *ever* see a recursive type, bail right away, clearly that is
// not simple.
if (seen.contains(ty))
return false;
seen.insert(ty);
// NOTE: We could probably support `{}` as a simple discriminant.
if (auto ttv = get<TableType>(ty); ttv && ttv->props.size() == 1 && !ttv->indexer)
{
auto prop = begin(ttv->props)->second;
return (!prop.readTy || isSimpleDiscriminant(*prop.readTy, seen)) && (!prop.writeTy || isSimpleDiscriminant(*prop.writeTy, seen));
}
if (auto nt = get<NegationType>(ty))
return isSimpleDiscriminant(nt->ty, seen);
return is<PrimitiveType, SingletonType, ExternType>(ty) || isApproximatelyTruthyType(ty) || isApproximatelyFalsyType(ty);
}
/**
* There are some types that are "simple", and thus easy to intersect against:
* - The "truthy" (`~(false?)`) and "falsy" (`false?`) types are simple.
* - Primitive types, singleton types, and extern types are simple
* - Table types are simple if they have no indexer, and have a single property
* who's read and write types are also simple.
* - Cyclic types are never simple.
*/
bool isSimpleDiscriminant(TypeId ty)
{
DenseHashSet<TypeId> seenSet{nullptr};
return isSimpleDiscriminant(ty, seenSet);
}
} // namespace
std::optional<TypeId> TypeSimplifier::intersectOne(TypeId target, TypeId discriminant) const
{
switch (relate(target, discriminant))
{
case Relation::Disjoint: // No A is a B or vice versa
return builtinTypes->neverType;
case Relation::Subset: // Every A is in B
case Relation::Coincident: // Every A is in B and vice versa
return target;
case Relation::Superset: // Every B is in A
return discriminant;
case Relation::Intersects:
default:
// Some As are in B and some Bs are in A. ex (number | string) <-> (string | boolean).
return std::nullopt;
}
}
std::optional<TypeId> TypeSimplifier::subtractOne(TypeId target, TypeId discriminant) const
{
target = follow(target);
discriminant = follow(discriminant);
if (auto nt = get<NegationType>(discriminant))
return intersectOne(target, nt->ty);
switch (relate(target, discriminant))
{
case Relation::Disjoint: // A v B is empty => A - B is equivalent to A
return target;
case Relation::Subset: // A v B is A => A - B is empty
case Relation::Coincident: // Same as above: A == B so A - B = {}
return builtinTypes->neverType;
case Relation::Superset:
case Relation::Intersects:
default:
return std::nullopt;
}
}
std::optional<Property> TypeSimplifier::intersectProperty(const Property& target, const Property& discriminant, DenseHashSet<TypeId>& seen) const
{
// NOTE: I invite the reader to refactor the below code as a fun coding
// exercise. It looks ugly to me, but I don't think we can make it
// any cleaner.
Property prop;
prop.deprecated = target.deprecated || discriminant.deprecated;
// We're trying to follow the following rules for both read and write types:
// * If the type is present on both properties, intersect it, and return
// `std::nullopt` if we fail.
// * If the type only exists on one property or the other, take that.
if (target.readTy && discriminant.readTy)
{
prop.readTy = intersectWithSimpleDiscriminant(*target.readTy, *discriminant.readTy, seen);
if (!prop.readTy)
return std::nullopt;
}
else if (target.readTy && !discriminant.readTy)
prop.readTy = target.readTy;
else if (!target.readTy && discriminant.readTy)
prop.readTy = discriminant.readTy;
if (target.writeTy && discriminant.writeTy)
{
prop.writeTy = intersectWithSimpleDiscriminant(*target.writeTy, *discriminant.writeTy, seen);
if (!prop.writeTy)
return std::nullopt;
}
else if (target.writeTy && !discriminant.writeTy)
prop.writeTy = target.writeTy;
else if (!target.writeTy && discriminant.writeTy)
prop.writeTy = discriminant.writeTy;
return {prop};
}
std::optional<TypeId> TypeSimplifier::intersectWithSimpleDiscriminant(TypeId target, TypeId discriminant, DenseHashSet<TypeId>& seen) const
{
if (seen.contains(target))
return std::nullopt;
target = follow(target);
discriminant = follow(discriminant);
if (auto ut = get<UnionType>(target))
{
seen.insert(target);
TypeIds options;
for (TypeId option : ut)
{
auto result = intersectWithSimpleDiscriminant(option, discriminant, seen);
if (!result)
return std::nullopt;
if (is<UnknownType>(result))
return builtinTypes->unknownType;
if (!is<NeverType>(*result))
options.insert(*result);
}
if (options.empty())
return builtinTypes->neverType;
if (options.size() == 1)
return *options.begin();
return arena->addType(UnionType{options.take()});
}
if (auto it = get<IntersectionType>(target))
{
seen.insert(target);
TypeIds parts;
for (TypeId part : it)
{
auto result = intersectWithSimpleDiscriminant(part, discriminant, seen);
if (!result)
return std::nullopt;
if (is<NeverType>(*result))
return builtinTypes->neverType;
if (auto subIntersection = get<IntersectionType>(*result))
{
for (TypeId subOption : subIntersection)
{
if (is<NeverType>(subOption))
return builtinTypes->neverType;
if (!is<UnknownType>(result))
parts.insert(*result);
}
}
else if (!is<UnknownType>(*result))
parts.insert(*result);
}
if (parts.empty())
return builtinTypes->unknownType;
if (parts.size() == 1)
return *parts.begin();
return arena->addType(IntersectionType{parts.take()});
}
if (auto ttv = get<TableType>(target))
{
if (auto discTtv = get<TableType>(discriminant))
{
// The precondition of this function is that `discriminant` is
// simple, so if it's a table it *must* be a sealed table with
// a single property and no indexer.
LUAU_ASSERT(discTtv->props.size() == 1 && !discTtv->indexer);
const auto discProp = begin(discTtv->props);
if (auto tyProp = ttv->props.find(discProp->first); tyProp != ttv->props.end())
{
auto property = intersectProperty(tyProp->second, discProp->second, seen);
if (!property)
return std::nullopt;
if (property->readTy && is<NeverType>(follow(property->readTy)))
return builtinTypes->neverType;
if (property->writeTy && is<NeverType>(follow(property->writeTy)))
return builtinTypes->neverType;
// If the property we get back is pointer identical to the
// original property, return the underlying property as an
// optimization.
if (tyProp->second.readTy == property->readTy && tyProp->second.writeTy == property->writeTy)
return target;
CloneState cs{builtinTypes};
TypeId result = shallowClone(target, *arena, cs, /* clonePersistentTypes */ true);
auto resultTtv = getMutable<TableType>(result);
LUAU_ASSERT(resultTtv);
resultTtv->props[tyProp->first] = *property;
// Shallow cloning clears out scopes, so let's put back the
// scope from the original type.
resultTtv->scope = ttv->scope;
return result;
}
CloneState cs{builtinTypes};
TypeId result = shallowClone(target, *arena, cs, /* clonePersistentTypes */ true);
// Shallow cloning clears out scopes, so let's put back the
// scope from the original type.
auto resultTtv = getMutable<TableType>(result);
LUAU_ASSERT(resultTtv);
resultTtv->props.emplace(discProp->first, discProp->second);
resultTtv->scope = ttv->scope;
return result;
}
// At this point, we're doing something like:
//
// { ... } & ~nil
//
// Which can be handled via fallthrough.
}
// FIXME: We could probably return to this.
if (is<FreeType, GenericType, BlockedType, PendingExpansionType, TypeFunctionInstanceType>(target))
return std::nullopt;
if (isApproximatelyTruthyType(discriminant))
return basicIntersectWithTruthy(target);
if (isApproximatelyTruthyType(target))
return basicIntersectWithTruthy(discriminant);
if (isApproximatelyFalsyType(discriminant))
return basicIntersectWithFalsy(target);
if (isApproximatelyFalsyType(target))
return basicIntersectWithFalsy(discriminant);
if (is<AnyType>(target))
return arena->addType(UnionType{{builtinTypes->errorType, discriminant}});
if (auto nty = get<NegationType>(discriminant))
return subtractOne(target, nty->ty);
return intersectOne(target, discriminant);
}
std::optional<TypeId> TypeSimplifier::intersectWithSimpleDiscriminant(TypeId target, TypeId discriminant) const
{
DenseHashSet<TypeId> seenSet{nullptr};
return intersectWithSimpleDiscriminant(target, discriminant, seenSet);
}
SimplifyResult simplifyIntersection(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, TypeId left, TypeId right) SimplifyResult simplifyIntersection(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, TypeId left, TypeId right)
{ {
TypeSimplifier s{builtinTypes, arena}; TypeSimplifier s{builtinTypes, arena};
@ -1581,4 +1937,25 @@ SimplifyResult simplifyUnion(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeAre
return SimplifyResult{res, std::move(s.blockedTypes)}; return SimplifyResult{res, std::move(s.blockedTypes)};
} }
std::optional<TypeId> intersectWithSimpleDiscriminant(
NotNull<BuiltinTypes> builtinTypes,
NotNull<TypeArena> arena,
TypeId target,
TypeId discriminant
)
{
if (!isSimpleDiscriminant(discriminant))
{
if (isSimpleDiscriminant(target))
return intersectWithSimpleDiscriminant(builtinTypes, arena, discriminant, target);
return std::nullopt;
}
TypeSimplifier s{builtinTypes, arena};
return s.intersectWithSimpleDiscriminant(target, discriminant);
}
} // namespace Luau } // namespace Luau

View file

@ -10,6 +10,7 @@
LUAU_FASTINTVARIABLE(LuauTarjanChildLimit, 10000) LUAU_FASTINTVARIABLE(LuauTarjanChildLimit, 10000)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTINTVARIABLE(LuauTarjanPreallocationSize, 256) LUAU_FASTINTVARIABLE(LuauTarjanPreallocationSize, 256)
LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps)
LUAU_FASTFLAG(LuauSolverAgnosticClone) LUAU_FASTFLAG(LuauSolverAgnosticClone)
namespace Luau namespace Luau
@ -196,7 +197,7 @@ void Tarjan::visitChildren(TypeId ty, int index)
visitChild(prop.writeTy); visitChild(prop.writeTy);
} }
else else
visitChild(prop.type()); visitChild(prop.type_DEPRECATED());
} }
if (ttv->indexer) if (ttv->indexer)
@ -245,7 +246,15 @@ void Tarjan::visitChildren(TypeId ty, int index)
else if (const ExternType* etv = get<ExternType>(ty)) else if (const ExternType* etv = get<ExternType>(ty))
{ {
for (const auto& [name, prop] : etv->props) for (const auto& [name, prop] : etv->props)
visitChild(prop.type()); {
if (FFlag::LuauSolverV2 && FFlag::LuauRemoveTypeCallsForReadWriteProps)
{
visitChild(prop.readTy);
visitChild(prop.writeTy);
}
else
visitChild(prop.type_DEPRECATED());
}
if (etv->parent) if (etv->parent)
visitChild(*etv->parent); visitChild(*etv->parent);
@ -782,7 +791,7 @@ void Substitution::replaceChildren(TypeId ty)
prop.writeTy = replace(prop.writeTy); prop.writeTy = replace(prop.writeTy);
} }
else else
prop.setType(replace(prop.type())); prop.setType(replace(prop.type_DEPRECATED()));
} }
if (ttv->indexer) if (ttv->indexer)
@ -831,7 +840,17 @@ void Substitution::replaceChildren(TypeId ty)
else if (ExternType* etv = getMutable<ExternType>(ty)) else if (ExternType* etv = getMutable<ExternType>(ty))
{ {
for (auto& [name, prop] : etv->props) for (auto& [name, prop] : etv->props)
prop.setType(replace(prop.type())); {
if (FFlag::LuauRemoveTypeCallsForReadWriteProps && FFlag::LuauSolverV2)
{
if (prop.readTy)
prop.readTy = replace(prop.readTy);
if (prop.writeTy)
prop.writeTy = replace(prop.writeTy);
}
else
prop.setType(replace(prop.type_DEPRECATED()));
}
if (etv->parent) if (etv->parent)
etv->parent = replace(*etv->parent); etv->parent = replace(*etv->parent);

View file

@ -21,6 +21,7 @@ LUAU_FASTINTVARIABLE(LuauSubtypingReasoningLimit, 100)
LUAU_FASTFLAG(LuauClipVariadicAnysFromArgsToGenericFuncs2) LUAU_FASTFLAG(LuauClipVariadicAnysFromArgsToGenericFuncs2)
LUAU_FASTFLAGVARIABLE(LuauSubtypingCheckFunctionGenericCounts) LUAU_FASTFLAGVARIABLE(LuauSubtypingCheckFunctionGenericCounts)
LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauEagerGeneralization4)
LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps)
namespace Luau namespace Luau
{ {
@ -1466,9 +1467,20 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Tabl
if (isCovariantWith(env, builtinTypes->stringType, subTable->indexer->indexType, scope).isSubtype) if (isCovariantWith(env, builtinTypes->stringType, subTable->indexer->indexType, scope).isSubtype)
{ {
if (superProp.isShared()) if (superProp.isShared())
results.push_back(isInvariantWith(env, subTable->indexer->indexResultType, superProp.type(), scope) {
if (FFlag::LuauRemoveTypeCallsForReadWriteProps)
{
results.push_back(isInvariantWith(env, subTable->indexer->indexResultType, *superProp.readTy, scope)
.withSubComponent(TypePath::TypeField::IndexResult) .withSubComponent(TypePath::TypeField::IndexResult)
.withSuperComponent(TypePath::Property::read(name))); .withSuperComponent(TypePath::Property::read(name)));
}
else
{
results.push_back(isInvariantWith(env, subTable->indexer->indexResultType, superProp.type_DEPRECATED(), scope)
.withSubComponent(TypePath::TypeField::IndexResult)
.withSuperComponent(TypePath::Property::read(name)));
}
}
else else
{ {
if (superProp.readTy) if (superProp.readTy)
@ -1643,10 +1655,22 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Prim
{ {
if (auto it = mttv->props.find("__index"); it != mttv->props.end()) if (auto it = mttv->props.find("__index"); it != mttv->props.end())
{ {
if (auto stringTable = get<TableType>(it->second.type()))
result.orElse( if (FFlag::LuauRemoveTypeCallsForReadWriteProps)
isCovariantWith(env, stringTable, superTable, scope).withSubPath(TypePath::PathBuilder().mt().readProp("__index").build()) {
); // the `string` metatable should not have any write-only types.
LUAU_ASSERT(*it->second.readTy);
if (auto stringTable = get<TableType>(*it->second.readTy))
result.orElse(isCovariantWith(env, stringTable, superTable, scope)
.withSubPath(TypePath::PathBuilder().mt().readProp("__index").build()));
}
else
{
if (auto stringTable = get<TableType>(it->second.type_DEPRECATED()))
result.orElse(isCovariantWith(env, stringTable, superTable, scope)
.withSubPath(TypePath::PathBuilder().mt().readProp("__index").build()));
}
} }
} }
} }
@ -1676,10 +1700,21 @@ SubtypingResult Subtyping::isCovariantWith(
{ {
if (auto it = mttv->props.find("__index"); it != mttv->props.end()) if (auto it = mttv->props.find("__index"); it != mttv->props.end())
{ {
if (auto stringTable = get<TableType>(it->second.type())) if (FFlag::LuauRemoveTypeCallsForReadWriteProps)
result.orElse( {
isCovariantWith(env, stringTable, superTable, scope).withSubPath(TypePath::PathBuilder().mt().readProp("__index").build()) // the `string` metatable should not have any write-only types.
); LUAU_ASSERT(*it->second.readTy);
if (auto stringTable = get<TableType>(*it->second.readTy))
result.orElse(isCovariantWith(env, stringTable, superTable, scope)
.withSubPath(TypePath::PathBuilder().mt().readProp("__index").build()));
}
else
{
if (auto stringTable = get<TableType>(it->second.type_DEPRECATED()))
result.orElse(isCovariantWith(env, stringTable, superTable, scope)
.withSubPath(TypePath::PathBuilder().mt().readProp("__index").build()));
}
} }
} }
} }
@ -1712,7 +1747,12 @@ SubtypingResult Subtyping::isCovariantWith(
SubtypingResult res{true}; SubtypingResult res{true};
if (superProp.isShared() && subProp.isShared()) if (superProp.isShared() && subProp.isShared())
res.andAlso(isInvariantWith(env, subProp.type(), superProp.type(), scope).withBothComponent(TypePath::Property::read(name))); {
if (FFlag::LuauRemoveTypeCallsForReadWriteProps)
res.andAlso(isInvariantWith(env, *subProp.readTy, *superProp.readTy, scope).withBothComponent(TypePath::Property::read(name)));
else
res.andAlso(isInvariantWith(env, subProp.type_DEPRECATED(), superProp.type_DEPRECATED(), scope).withBothComponent(TypePath::Property::read(name)));
}
else else
{ {
if (superProp.readTy.has_value() && subProp.readTy.has_value()) if (superProp.readTy.has_value() && subProp.readTy.has_value())

View file

@ -10,7 +10,8 @@
#include <unordered_map> #include <unordered_map>
#include <unordered_set> #include <unordered_set>
LUAU_FASTFLAG(LuauSolverV2); LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps)
namespace Luau namespace Luau
{ {
@ -188,7 +189,29 @@ void StateDot::visitChildren(TypeId ty, int index)
return visitChild(*t.boundTo, index, "boundTo"); return visitChild(*t.boundTo, index, "boundTo");
for (const auto& [name, prop] : t.props) for (const auto& [name, prop] : t.props)
visitChild(prop.type(), index, name.c_str()); {
if (FFlag::LuauRemoveTypeCallsForReadWriteProps)
{
if (prop.isShared())
visitChild(*prop.readTy, index, name.c_str());
else
{
if (prop.readTy)
{
std::string readName = "read " + name;
visitChild(*prop.readTy, index, readName.c_str());
}
if (prop.writeTy)
{
std::string writeName = "write " + name;
visitChild(*prop.writeTy, index, writeName.c_str());
}
}
}
else
visitChild(prop.type_DEPRECATED(), index, name.c_str());
}
if (t.indexer) if (t.indexer)
{ {
visitChild(t.indexer->indexType, index, "[index]"); visitChild(t.indexer->indexType, index, "[index]");
@ -306,7 +329,29 @@ void StateDot::visitChildren(TypeId ty, int index)
finishNode(); finishNode();
for (const auto& [name, prop] : t.props) for (const auto& [name, prop] : t.props)
visitChild(prop.type(), index, name.c_str()); {
if (FFlag::LuauRemoveTypeCallsForReadWriteProps)
{
if (prop.isShared())
visitChild(*prop.readTy, index, name.c_str());
else
{
if (prop.readTy)
{
std::string readName = "read " + name;
visitChild(*prop.readTy, index, readName.c_str());
}
if (prop.writeTy)
{
std::string writeName = "write " + name;
visitChild(*prop.writeTy, index, writeName.c_str());
}
}
}
else
visitChild(prop.type_DEPRECATED(), index, name.c_str());
}
if (t.parent) if (t.parent)
visitChild(*t.parent, index, "[parent]"); visitChild(*t.parent, index, "[parent]");

View file

@ -22,6 +22,7 @@
LUAU_FASTFLAGVARIABLE(LuauEnableDenseTableAlias) LUAU_FASTFLAGVARIABLE(LuauEnableDenseTableAlias)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps)
/* /*
* Enables increasing levels of verbosity for Luau type names when stringifying. * Enables increasing levels of verbosity for Luau type names when stringifying.
@ -409,7 +410,10 @@ struct TypeStringifier
if (prop.isShared()) if (prop.isShared())
{ {
emitKey(name); emitKey(name);
stringify(prop.type()); if (FFlag::LuauRemoveTypeCallsForReadWriteProps)
stringify(*prop.readTy);
else
stringify(prop.type_DEPRECATED());
return; return;
} }
@ -440,7 +444,7 @@ struct TypeStringifier
return _newStringify(name, prop); return _newStringify(name, prop);
emitKey(name); emitKey(name);
stringify(prop.type()); stringify(prop.type_DEPRECATED());
} }
void stringify(TypePackId tp); void stringify(TypePackId tp);

View file

@ -41,8 +41,6 @@
#include <stdexcept> #include <stdexcept>
#include <optional> #include <optional>
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
namespace Luau namespace Luau
{ {
@ -302,7 +300,7 @@ struct ArcCollector : public AstVisitor
bool visit(AstTypePack* node) override bool visit(AstTypePack* node) override
{ {
return FFlag::LuauStoreReturnTypesAsPackOnAst; return true;
} }
}; };

File diff suppressed because it is too large Load diff

View file

@ -30,6 +30,7 @@ LUAU_FASTINTVARIABLE(LuauTableTypeMaximumStringifierLength, 0)
LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAG(LuauSubtypingCheckFunctionGenericCounts) LUAU_FASTFLAG(LuauSubtypingCheckFunctionGenericCounts)
LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps)
namespace Luau namespace Luau
{ {
@ -708,8 +709,11 @@ Property Property::create(std::optional<TypeId> read, std::optional<TypeId> writ
} }
} }
TypeId Property::type() const TypeId Property::type_DEPRECATED() const
{ {
if (FFlag::LuauRemoveTypeCallsForReadWriteProps)
LUAU_ASSERT(!FFlag::LuauSolverV2);
LUAU_ASSERT(readTy); LUAU_ASSERT(readTy);
return *readTy; return *readTy;
} }
@ -834,7 +838,7 @@ bool areEqual(SeenSet& seen, const TableType& lhs, const TableType& rhs)
if (l->first != r->first) if (l->first != r->first)
return false; return false;
if (FFlag::LuauSolverV2 && FFlag::LuauSubtypingCheckFunctionGenericCounts) if (FFlag::LuauSolverV2 && (FFlag::LuauSubtypingCheckFunctionGenericCounts || FFlag::LuauRemoveTypeCallsForReadWriteProps))
{ {
if (l->second.readTy && r->second.readTy) if (l->second.readTy && r->second.readTy)
{ {
@ -852,7 +856,7 @@ bool areEqual(SeenSet& seen, const TableType& lhs, const TableType& rhs)
else if (l->second.writeTy || r->second.writeTy) else if (l->second.writeTy || r->second.writeTy)
return false; return false;
} }
else if (!areEqual(seen, *l->second.type(), *r->second.type())) else if (!areEqual(seen, *l->second.type_DEPRECATED(), *r->second.type_DEPRECATED()))
return false; return false;
++l; ++l;
++r; ++r;
@ -1082,7 +1086,18 @@ void persist(TypeId ty)
LUAU_ASSERT(ttv->state != TableState::Free && ttv->state != TableState::Unsealed); LUAU_ASSERT(ttv->state != TableState::Free && ttv->state != TableState::Unsealed);
for (const auto& [_name, prop] : ttv->props) for (const auto& [_name, prop] : ttv->props)
queue.push_back(prop.type()); {
if (FFlag::LuauRemoveTypeCallsForReadWriteProps && FFlag::LuauSolverV2)
{
if (prop.readTy)
queue.push_back(*prop.readTy);
if (prop.writeTy)
queue.push_back(*prop.writeTy);
}
else
queue.push_back(prop.type_DEPRECATED());
}
if (ttv->indexer) if (ttv->indexer)
{ {
@ -1093,7 +1108,17 @@ void persist(TypeId ty)
else if (auto etv = get<ExternType>(t)) else if (auto etv = get<ExternType>(t))
{ {
for (const auto& [_name, prop] : etv->props) for (const auto& [_name, prop] : etv->props)
queue.push_back(prop.type()); {
if (FFlag::LuauRemoveTypeCallsForReadWriteProps && FFlag::LuauSolverV2)
{
if (prop.readTy)
queue.push_back(*prop.readTy);
if (prop.writeTy)
queue.push_back(*prop.writeTy);
}
else
queue.push_back(prop.type_DEPRECATED());
}
} }
else if (auto utv = get<UnionType>(t)) else if (auto utv = get<UnionType>(t))
{ {

View file

@ -3,6 +3,7 @@
#include "Luau/TypeArena.h" #include "Luau/TypeArena.h"
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeArena); LUAU_FASTFLAGVARIABLE(DebugLuauFreezeArena);
LUAU_FASTFLAG(LuauTrackTypeAllocations)
namespace Luau namespace Luau
{ {
@ -114,6 +115,29 @@ TypePackId TypeArena::addTypePackFunction(const TypePackFunction& function, std:
return addTypePack(TypeFunctionInstanceTypePack{NotNull{&function}, std::move(typeArguments), std::move(packArguments)}); return addTypePack(TypeFunctionInstanceTypePack{NotNull{&function}, std::move(typeArguments), std::move(packArguments)});
} }
void TypeArena::recordSingletonStats(const NotNull<SingletonType> singleton)
{
LUAU_ASSERT(FFlag::LuauTrackTypeAllocations);
auto record = [this](auto&& s)
{
using T = std::decay_t<decltype(s)>;
if constexpr (std::is_same_v<T, BooleanSingleton>)
boolSingletonsMinted += 1;
else if constexpr (std::is_same_v<T, StringSingleton>)
{
strSingletonsMinted += 1;
if (!s.value.empty())
uniqueStrSingletonsMinted.insert(s.value);
}
else
static_assert(always_false_v<T>, "Missing allocation count support for this kind of singleton");
};
visit(record, singleton->variant);
}
void freeze(TypeArena& arena) void freeze(TypeArena& arena)
{ {
if (!FFlag::DebugLuauFreezeArena) if (!FFlag::DebugLuauFreezeArena)

View file

@ -1,6 +1,7 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/TypeAttach.h" #include "Luau/TypeAttach.h"
#include "Luau/Ast.h"
#include "Luau/Error.h" #include "Luau/Error.h"
#include "Luau/Module.h" #include "Luau/Module.h"
#include "Luau/RecursionCounter.h" #include "Luau/RecursionCounter.h"
@ -13,8 +14,7 @@
#include <string> #include <string>
LUAU_FASTFLAG(LuauStoreCSTData2) LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
static char* allocateString(Luau::Allocator& allocator, std::string_view contents) static char* allocateString(Luau::Allocator& allocator, std::string_view contents)
{ {
@ -197,11 +197,45 @@ public:
char* name = allocateString(*allocator, propName); char* name = allocateString(*allocator, propName);
if (FFlag::LuauRemoveTypeCallsForReadWriteProps)
{
if (prop.isShared())
{
props.data[idx].name = AstName(name); props.data[idx].name = AstName(name);
props.data[idx].type = Luau::visit(*this, prop.type()->ty); props.data[idx].type = Luau::visit(*this, (*prop.readTy)->ty);
props.data[idx].access = AstTableAccess::ReadWrite;
props.data[idx].location = Location(); props.data[idx].location = Location();
idx++; idx++;
} }
else
{
if (prop.readTy)
{
props.data[idx].name = AstName(name);
props.data[idx].type = Luau::visit(*this, (*prop.readTy)->ty);
props.data[idx].access = AstTableAccess::Read;
props.data[idx].location = Location();
idx++;
}
if (prop.writeTy)
{
props.data[idx].name = AstName(name);
props.data[idx].type = Luau::visit(*this, (*prop.writeTy)->ty);
props.data[idx].access = AstTableAccess::Write;
props.data[idx].location = Location();
idx++;
}
}
}
else
{
props.data[idx].name = AstName(name);
props.data[idx].type = Luau::visit(*this, prop.type_DEPRECATED()->ty);
props.data[idx].location = Location();
idx++;
}
}
AstTableIndexer* indexer = nullptr; AstTableIndexer* indexer = nullptr;
if (ttv.indexer) if (ttv.indexer)
@ -238,11 +272,45 @@ public:
{ {
char* name = allocateString(*allocator, propName); char* name = allocateString(*allocator, propName);
props.data[idx].name = AstName{name}; if (FFlag::LuauRemoveTypeCallsForReadWriteProps)
props.data[idx].type = Luau::visit(*this, prop.type()->ty); {
if (prop.isShared())
{
props.data[idx].name = AstName(name);
props.data[idx].type = Luau::visit(*this, (*prop.readTy)->ty);
props.data[idx].access = AstTableAccess::ReadWrite;
props.data[idx].location = Location(); props.data[idx].location = Location();
idx++; idx++;
} }
else
{
if (prop.readTy)
{
props.data[idx].name = AstName(name);
props.data[idx].type = Luau::visit(*this, (*prop.readTy)->ty);
props.data[idx].access = AstTableAccess::Read;
props.data[idx].location = Location();
idx++;
}
if (prop.writeTy)
{
props.data[idx].name = AstName(name);
props.data[idx].type = Luau::visit(*this, (*prop.writeTy)->ty);
props.data[idx].access = AstTableAccess::Write;
props.data[idx].location = Location();
idx++;
}
}
}
else
{
props.data[idx].name = AstName{name};
props.data[idx].type = Luau::visit(*this, prop.type_DEPRECATED()->ty);
props.data[idx].location = Location();
idx++;
}
}
AstTableIndexer* indexer = nullptr; AstTableIndexer* indexer = nullptr;
if (etv.indexer) if (etv.indexer)
@ -308,8 +376,7 @@ public:
std::optional<AstArgumentName>* arg = &argNames.data[i++]; std::optional<AstArgumentName>* arg = &argNames.data[i++];
if (el) if (el)
new (arg) new (arg) std::optional<AstArgumentName>(AstArgumentName(AstName(el->name.c_str()), Location()));
std::optional<AstArgumentName>(AstArgumentName(AstName(el->name.c_str()), FFlag::LuauStoreCSTData2 ? Location() : el->location));
else else
new (arg) std::optional<AstArgumentName>(); new (arg) std::optional<AstArgumentName>();
} }
@ -329,20 +396,11 @@ public:
if (retTail) if (retTail)
retTailAnnotation = rehydrate(*retTail); retTailAnnotation = rehydrate(*retTail);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
auto returnAnnotation = allocator->alloc<AstTypePackExplicit>(Location(), AstTypeList{returnTypes, retTailAnnotation}); auto returnAnnotation = allocator->alloc<AstTypePackExplicit>(Location(), AstTypeList{returnTypes, retTailAnnotation});
return allocator->alloc<AstTypeFunction>( return allocator->alloc<AstTypeFunction>(
Location(), generics, genericPacks, AstTypeList{argTypes, argTailAnnotation}, argNames, returnAnnotation Location(), generics, genericPacks, AstTypeList{argTypes, argTailAnnotation}, argNames, returnAnnotation
); );
} }
else
{
return allocator->alloc<AstTypeFunction>(
Location(), generics, genericPacks, AstTypeList{argTypes, argTailAnnotation}, argNames, AstTypeList{returnTypes, retTailAnnotation}
);
}
}
AstType* operator()(const ErrorType&) AstType* operator()(const ErrorType&)
{ {
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName("Unifiable<Error>"), std::nullopt, Location()); return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName("Unifiable<Error>"), std::nullopt, Location());
@ -596,8 +654,6 @@ public:
visitLocal(arg); visitLocal(arg);
} }
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
if (!fn->returnAnnotation) if (!fn->returnAnnotation)
{ {
if (auto result = getScope(fn->body->location)) if (auto result = getScope(fn->body->location))
@ -613,25 +669,6 @@ public:
fn->returnAnnotation = allocator->alloc<AstTypePackExplicit>(Location(), AstTypeList{typeAstPack(ret), variadicAnnotation}); fn->returnAnnotation = allocator->alloc<AstTypePackExplicit>(Location(), AstTypeList{typeAstPack(ret), variadicAnnotation});
} }
} }
}
else
{
if (!fn->returnAnnotation_DEPRECATED)
{
if (auto result = getScope(fn->body->location))
{
TypePackId ret = result->returnType;
AstTypePack* variadicAnnotation = nullptr;
const auto& [v, tail] = flatten(ret);
if (tail)
variadicAnnotation = TypeRehydrationVisitor(allocator, &syntheticNames).rehydrate(*tail);
fn->returnAnnotation_DEPRECATED = AstTypeList{typeAstPack(ret), variadicAnnotation};
}
}
}
return true; return true;
} }

View file

@ -31,13 +31,13 @@
LUAU_FASTFLAG(DebugLuauMagicTypes) LUAU_FASTFLAG(DebugLuauMagicTypes)
LUAU_FASTFLAGVARIABLE(LuauTypeCheckerStricterIndexCheck) LUAU_FASTFLAGVARIABLE(LuauTypeCheckerStricterIndexCheck)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
LUAU_FASTFLAG(LuauEnableWriteOnlyProperties) LUAU_FASTFLAG(LuauEnableWriteOnlyProperties)
LUAU_FASTFLAG(LuauNewNonStrictFixGenericTypePacks) LUAU_FASTFLAG(LuauNewNonStrictFixGenericTypePacks)
LUAU_FASTFLAGVARIABLE(LuauReportSubtypingErrors) LUAU_FASTFLAGVARIABLE(LuauReportSubtypingErrors)
LUAU_FASTFLAGVARIABLE(LuauSkipMalformedTypeAliases) LUAU_FASTFLAGVARIABLE(LuauSkipMalformedTypeAliases)
LUAU_FASTFLAGVARIABLE(LuauTableLiteralSubtypeSpecificCheck2) LUAU_FASTFLAGVARIABLE(LuauTableLiteralSubtypeSpecificCheck2)
LUAU_FASTFLAG(LuauSubtypingCheckFunctionGenericCounts) LUAU_FASTFLAG(LuauSubtypingCheckFunctionGenericCounts)
LUAU_FASTFLAG(LuauTableLiteralSubtypeCheckFunctionCalls)
namespace Luau namespace Luau
{ {
@ -1316,10 +1316,7 @@ void TypeChecker2::visit(AstStatDeclareFunction* stat)
{ {
visitGenerics(stat->generics, stat->genericPacks); visitGenerics(stat->generics, stat->genericPacks);
visit(stat->params); visit(stat->params);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
visit(stat->retTypes); visit(stat->retTypes);
else
visit(stat->retTypes_DEPRECATED);
} }
void TypeChecker2::visit(AstStatDeclareGlobal* stat) void TypeChecker2::visit(AstStatDeclareGlobal* stat)
@ -1573,6 +1570,58 @@ void TypeChecker2::visitCall(AstExprCall* call)
argExprs.push_back(indexExpr->expr); argExprs.push_back(indexExpr->expr);
} }
if (FFlag::LuauTableLiteralSubtypeCheckFunctionCalls)
{
// FIXME: Similar to bidirectional inference prior, this does not support
// overloaded functions nor generic types (yet).
if (auto fty = get<FunctionType>(fnTy); fty && fty->generics.empty() && fty->genericPacks.empty() && call->args.size > 0)
{
size_t selfOffset = call->self ? 1 : 0;
auto [paramsHead, _] = extendTypePack(module->internalTypes, builtinTypes, fty->argTypes, call->args.size + selfOffset);
for (size_t idx = 0; idx < call->args.size - 1; ++idx)
{
auto argExpr = call->args.data[idx];
auto argExprType = lookupType(argExpr);
argExprs.push_back(argExpr);
if (idx + selfOffset >= paramsHead.size() || isErrorSuppressing(argExpr->location, argExprType))
{
args.head.push_back(argExprType);
continue;
}
testLiteralOrAstTypeIsSubtype(argExpr, paramsHead[idx + selfOffset]);
args.head.push_back(paramsHead[idx + selfOffset]);
}
auto lastExpr = call->args.data[call->args.size - 1];
argExprs.push_back(lastExpr);
if (auto argTail = module->astTypePacks.find(lastExpr))
{
auto [lastExprHead, lastExprTail] = flatten(*argTail);
args.head.insert(args.head.end(), lastExprHead.begin(), lastExprHead.end());
args.tail = lastExprTail;
}
else if (paramsHead.size() >= call->args.size + selfOffset)
{
auto lastType = paramsHead[call->args.size - 1 + selfOffset];
auto lastExprType = lookupType(lastExpr);
if (isErrorSuppressing(lastExpr->location, lastExprType))
{
args.head.push_back(lastExprType);
}
else
{
testLiteralOrAstTypeIsSubtype(lastExpr, lastType);
args.head.push_back(lastType);
}
}
else
args.tail = builtinTypes->anyTypePack;
}
else
{
for (size_t i = 0; i < call->args.size; ++i) for (size_t i = 0; i < call->args.size; ++i)
{ {
AstExpr* arg = call->args.data[i]; AstExpr* arg = call->args.data[i];
@ -1594,6 +1643,32 @@ void TypeChecker2::visitCall(AstExprCall* call)
else else
args.head.push_back(builtinTypes->anyType); args.head.push_back(builtinTypes->anyType);
} }
}
}
else
{
for (size_t i = 0; i < call->args.size; ++i)
{
AstExpr* arg = call->args.data[i];
argExprs.push_back(arg);
TypeId* argTy = module->astTypes.find(arg);
if (argTy)
args.head.push_back(*argTy);
else if (i == call->args.size - 1)
{
if (auto argTail = module->astTypePacks.find(arg))
{
auto [head, tail] = flatten(*argTail);
args.head.insert(args.head.end(), head.begin(), head.end());
args.tail = tail;
}
else
args.tail = builtinTypes->anyTypePack;
}
else
args.head.push_back(builtinTypes->anyType);
}
}
TypePackId argsTp = module->internalTypes.addTypePack(args); TypePackId argsTp = module->internalTypes.addTypePack(args);
if (auto ftv = get<FunctionType>(follow(*originalCallTy))) if (auto ftv = get<FunctionType>(follow(*originalCallTy)))
@ -1964,16 +2039,8 @@ void TypeChecker2::visit(AstExprFunction* fn)
visit(fn->body); visit(fn->body);
// we need to typecheck the return annotation itself, if it exists. // we need to typecheck the return annotation itself, if it exists.
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
if (fn->returnAnnotation) if (fn->returnAnnotation)
visit(fn->returnAnnotation); visit(fn->returnAnnotation);
}
else
{
if (fn->returnAnnotation_DEPRECATED)
visit(*fn->returnAnnotation_DEPRECATED);
}
// If the function type has a function annotation, we need to see if we can suggest an annotation // If the function type has a function annotation, we need to see if we can suggest an annotation
@ -2751,10 +2818,7 @@ void TypeChecker2::visit(AstTypeFunction* ty)
{ {
visitGenerics(ty->generics, ty->genericPacks); visitGenerics(ty->generics, ty->genericPacks);
visit(ty->argTypes); visit(ty->argTypes);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
visit(ty->returnTypes); visit(ty->returnTypes);
else
visit(ty->returnTypes_DEPRECATED);
} }
void TypeChecker2::visit(AstTypeTypeof* ty) void TypeChecker2::visit(AstTypeTypeof* ty)

View file

@ -52,11 +52,13 @@ LUAU_FASTFLAG(LuauEagerGeneralization4)
LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauEagerGeneralization4)
LUAU_FASTFLAGVARIABLE(DebugLuauLogTypeFamilies) LUAU_FASTFLAGVARIABLE(DebugLuauLogTypeFamilies)
LUAU_FASTFLAGVARIABLE(LuauNarrowIntersectionNevers)
LUAU_FASTFLAGVARIABLE(LuauNotAllBinaryTypeFunsHaveDefaults) LUAU_FASTFLAGVARIABLE(LuauNotAllBinaryTypeFunsHaveDefaults)
LUAU_FASTFLAG(LuauUserTypeFunctionAliases) LUAU_FASTFLAG(LuauUserTypeFunctionAliases)
LUAU_FASTFLAG(LuauUpdateGetMetatableTypeSignature) LUAU_FASTFLAG(LuauUpdateGetMetatableTypeSignature)
LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps)
LUAU_FASTFLAGVARIABLE(LuauOccursCheckForRefinement) LUAU_FASTFLAGVARIABLE(LuauOccursCheckForRefinement)
LUAU_FASTFLAG(LuauRefineTablesWithReadType)
LUAU_FASTFLAGVARIABLE(LuauEmptyStringInKeyOf)
namespace Luau namespace Luau
{ {
@ -370,7 +372,7 @@ struct TypeFunctionReducer
} }
if (FFlag::DebugLuauLogTypeFamilies) if (FFlag::DebugLuauLogTypeFamilies)
printf("%s -> %s\n", toString(subject, {true}).c_str(), toString(replacement, {true}).c_str()); printf("%s => %s\n", toString(subject, {true}).c_str(), toString(replacement, {true}).c_str());
asMutable(subject)->ty.template emplace<Unifiable::Bound<T>>(replacement); asMutable(subject)->ty.template emplace<Unifiable::Bound<T>>(replacement);
@ -2259,38 +2261,11 @@ struct ContainsRefinableType : TypeOnceVisitor
namespace namespace
{ {
bool isApproximateFalsy(TypeId ty)
{
ty = follow(ty);
bool seenNil = false;
bool seenFalse = false;
if (auto ut = get<UnionType>(ty))
{
for (auto option : ut)
{
if (auto pt = get<PrimitiveType>(option); pt && pt->type == PrimitiveType::NilType)
seenNil = true;
else if (auto st = get<SingletonType>(option); st && st->variant == BooleanSingleton{false})
seenFalse = true;
else
return false;
}
}
return seenFalse && seenNil;
}
bool isApproximateTruthy(TypeId ty) bool isTruthyOrFalsyType(TypeId ty)
{ {
ty = follow(ty); ty = follow(ty);
if (auto nt = get<NegationType>(ty)) return isApproximatelyTruthyType(ty) || isApproximatelyFalsyType(ty);
return isApproximateFalsy(nt->ty);
return false;
}
bool isSimpleDiscriminant(TypeId ty)
{
ty = follow(ty);
return isApproximateTruthy(ty) || isApproximateFalsy(ty);
} }
struct RefineTypeScrubber : public Substitution struct RefineTypeScrubber : public Substitution
@ -2486,6 +2461,13 @@ TypeFunctionReductionResult<TypeId> refineTypeFunction(
if (!crt.found) if (!crt.found)
return {target, {}}; return {target, {}};
if (FFlag::LuauRefineTablesWithReadType)
{
if (auto ty = intersectWithSimpleDiscriminant(ctx->builtins, ctx->arena, target, discriminant))
return {*ty, {}};
}
// NOTE: This block causes us to refine too early in some cases.
if (auto negation = get<NegationType>(discriminant)) if (auto negation = get<NegationType>(discriminant))
{ {
if (auto primitive = get<PrimitiveType>(follow(negation->ty)); primitive && primitive->type == PrimitiveType::NilType) if (auto primitive = get<PrimitiveType>(follow(negation->ty)); primitive && primitive->type == PrimitiveType::NilType)
@ -2497,12 +2479,8 @@ TypeFunctionReductionResult<TypeId> refineTypeFunction(
// If the target type is a table, then simplification already implements the logic to deal with refinements properly since the // If the target type is a table, then simplification already implements the logic to deal with refinements properly since the
// type of the discriminant is guaranteed to only ever be an (arbitrarily-nested) table of a single property type. // type of the discriminant is guaranteed to only ever be an (arbitrarily-nested) table of a single property type.
// We also fire for simple discriminants such as false? and ~(false?): the falsy and truthy types respectively // We also fire for simple discriminants such as false? and ~(false?): the falsy and truthy types respectively.
// NOTE: It would be nice to be able to do a simple intersection for something like: if (is<TableType>(target) || isTruthyOrFalsyType(discriminant))
//
// { a: A, b: B, ... } & { x: X }
//
if (is<TableType>(target) || isSimpleDiscriminant(discriminant))
{ {
SimplifyResult result = simplifyIntersection(ctx->builtins, ctx->arena, target, discriminant); SimplifyResult result = simplifyIntersection(ctx->builtins, ctx->arena, target, discriminant);
if (FFlag::LuauEagerGeneralization4) if (FFlag::LuauEagerGeneralization4)
@ -2551,6 +2529,7 @@ TypeFunctionReductionResult<TypeId> refineTypeFunction(
return {resultTy, {}}; return {resultTy, {}};
} }
}; };
// refine target with each discriminant type in sequence (reverse of insertion order) // refine target with each discriminant type in sequence (reverse of insertion order)
@ -2749,10 +2728,20 @@ TypeFunctionReductionResult<TypeId> intersectTypeFunction(
if (get<NoRefineType>(ty)) if (get<NoRefineType>(ty))
continue; continue;
if (FFlag::LuauRefineTablesWithReadType)
{
if (auto simpleResult = intersectWithSimpleDiscriminant(ctx->builtins, ctx->arena, resultTy, ty))
{
if (get<NeverType>(*simpleResult))
unintersectableTypes.insert(follow(ty));
else
resultTy = *simpleResult;
continue;
}
}
SimplifyResult result = simplifyIntersection(ctx->builtins, ctx->arena, resultTy, ty); SimplifyResult result = simplifyIntersection(ctx->builtins, ctx->arena, resultTy, ty);
if (FFlag::LuauNarrowIntersectionNevers)
{
// If simplifying the intersection returned never, note the type we tried to intersect it with, and continue trying to intersect with the // If simplifying the intersection returned never, note the type we tried to intersect it with, and continue trying to intersect with the
// rest // rest
if (get<NeverType>(result.result)) if (get<NeverType>(result.result))
@ -2760,8 +2749,6 @@ TypeFunctionReductionResult<TypeId> intersectTypeFunction(
unintersectableTypes.insert(follow(ty)); unintersectableTypes.insert(follow(ty));
continue; continue;
} }
}
for (TypeId blockedType : result.blockedTypes) for (TypeId blockedType : result.blockedTypes)
{ {
if (!get<GenericType>(blockedType)) if (!get<GenericType>(blockedType))
@ -2771,8 +2758,6 @@ TypeFunctionReductionResult<TypeId> intersectTypeFunction(
resultTy = result.result; resultTy = result.result;
} }
if (FFlag::LuauNarrowIntersectionNevers)
{
if (!unintersectableTypes.empty()) if (!unintersectableTypes.empty())
{ {
unintersectableTypes.insert(resultTy); unintersectableTypes.insert(resultTy);
@ -2787,8 +2772,6 @@ TypeFunctionReductionResult<TypeId> intersectTypeFunction(
return {*unintersectableTypes.begin(), Reduction::MaybeOk, {}, {}}; return {*unintersectableTypes.begin(), Reduction::MaybeOk, {}, {}};
} }
} }
}
// if the intersection simplifies to `never`, this gives us bad autocomplete. // if the intersection simplifies to `never`, this gives us bad autocomplete.
// we'll just produce the intersection plainly instead, but this might be revisitable // we'll just produce the intersection plainly instead, but this might be revisitable
// if we ever give `never` some kind of "explanation" trail. // if we ever give `never` some kind of "explanation" trail.
@ -2804,7 +2787,7 @@ TypeFunctionReductionResult<TypeId> intersectTypeFunction(
// computes the keys of `ty` into `result` // computes the keys of `ty` into `result`
// `isRaw` parameter indicates whether or not we should follow __index metamethods // `isRaw` parameter indicates whether or not we should follow __index metamethods
// returns `false` if `result` should be ignored because the answer is "all strings" // returns `false` if `result` should be ignored because the answer is "all strings"
bool computeKeysOf(TypeId ty, Set<std::string>& result, DenseHashSet<TypeId>& seen, bool isRaw, NotNull<TypeFunctionContext> ctx) bool computeKeysOf_DEPRECATED(TypeId ty, Set<std::string>& result, DenseHashSet<TypeId>& seen, bool isRaw, NotNull<TypeFunctionContext> ctx)
{ {
// if the type is the top table type, the answer is just "all strings" // if the type is the top table type, the answer is just "all strings"
if (get<PrimitiveType>(ty)) if (get<PrimitiveType>(ty))
@ -2830,6 +2813,89 @@ bool computeKeysOf(TypeId ty, Set<std::string>& result, DenseHashSet<TypeId>& se
return true; return true;
} }
// otherwise, we have a metatable to deal with
if (auto metatableTy = get<MetatableType>(ty))
{
bool res = true;
if (!isRaw)
{
// findMetatableEntry demands the ability to emit errors, so we must give it
// the necessary state to do that, even if we intend to just eat the errors.
ErrorVec dummy;
std::optional<TypeId> mmType = findMetatableEntry(ctx->builtins, dummy, ty, "__index", Location{});
if (mmType)
res = res && computeKeysOf_DEPRECATED(*mmType, result, seen, isRaw, ctx);
}
res = res && computeKeysOf_DEPRECATED(metatableTy->table, result, seen, isRaw, ctx);
return res;
}
if (auto classTy = get<ExternType>(ty))
{
for (auto [key, _] : classTy->props) // NOLINT(performance-for-range-copy)
result.insert(key);
bool res = true;
if (classTy->metatable && !isRaw)
{
// findMetatableEntry demands the ability to emit errors, so we must give it
// the necessary state to do that, even if we intend to just eat the errors.
ErrorVec dummy;
std::optional<TypeId> mmType = findMetatableEntry(ctx->builtins, dummy, ty, "__index", Location{});
if (mmType)
res = res && computeKeysOf_DEPRECATED(*mmType, result, seen, isRaw, ctx);
}
if (classTy->parent)
res = res && computeKeysOf_DEPRECATED(follow(*classTy->parent), result, seen, isRaw, ctx);
return res;
}
// this should not be reachable since the type should be a valid tables or extern types part from normalization.
LUAU_ASSERT(false);
return false;
}
namespace {
/**
* Computes the keys of `ty` into `result`
* `isRaw` parameter indicates whether or not we should follow __index metamethods
* returns `false` if `result` should be ignored because the answer is "all strings"
*/
bool computeKeysOf(TypeId ty, Set<std::optional<std::string>>& result, DenseHashSet<TypeId>& seen, bool isRaw, NotNull<TypeFunctionContext> ctx)
{
// if the type is the top table type, the answer is just "all strings"
if (get<PrimitiveType>(ty))
return false;
// if we've already seen this type, we can do nothing
if (seen.contains(ty))
return true;
seen.insert(ty);
// if we have a particular table type, we can insert the keys
if (auto tableTy = get<TableType>(ty))
{
if (tableTy->indexer)
{
// if we have a string indexer, the answer is, again, "all strings"
if (isString(tableTy->indexer->indexType))
return false;
}
for (const auto& [key, _] : tableTy->props)
result.insert(key);
return true;
}
// otherwise, we have a metatable to deal with // otherwise, we have a metatable to deal with
if (auto metatableTy = get<MetatableType>(ty)) if (auto metatableTy = get<MetatableType>(ty))
{ {
@ -2853,7 +2919,7 @@ bool computeKeysOf(TypeId ty, Set<std::string>& result, DenseHashSet<TypeId>& se
if (auto classTy = get<ExternType>(ty)) if (auto classTy = get<ExternType>(ty))
{ {
for (auto [key, _] : classTy->props) for (const auto& [key, _] : classTy->props)
result.insert(key); result.insert(key);
bool res = true; bool res = true;
@ -2879,6 +2945,8 @@ bool computeKeysOf(TypeId ty, Set<std::string>& result, DenseHashSet<TypeId>& se
return false; return false;
} }
}
TypeFunctionReductionResult<TypeId> keyofFunctionImpl( TypeFunctionReductionResult<TypeId> keyofFunctionImpl(
const std::vector<TypeId>& typeParams, const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams, const std::vector<TypePackId>& packParams,
@ -2910,8 +2978,11 @@ TypeFunctionReductionResult<TypeId> keyofFunctionImpl(
normTy->hasThreads() || normTy->hasBuffers() || normTy->hasFunctions() || normTy->hasTyvars()) normTy->hasThreads() || normTy->hasBuffers() || normTy->hasFunctions() || normTy->hasTyvars())
return {std::nullopt, Reduction::Erroneous, {}, {}}; return {std::nullopt, Reduction::Erroneous, {}, {}};
// we're going to collect the keys in here if (FFlag::LuauEmptyStringInKeyOf)
Set<std::string> keys{{}}; {
// We're going to collect the keys in here, and we use optional strings
// so that we can differentiate between the empty string and _no_ string.
Set<std::optional<std::string>> keys{std::nullopt};
// computing the keys for extern types // computing the keys for extern types
if (normTy->hasExternTypes()) if (normTy->hasExternTypes())
@ -2934,7 +3005,7 @@ TypeFunctionReductionResult<TypeId> keyofFunctionImpl(
{ {
seen.clear(); // we'll reuse the same seen set seen.clear(); // we'll reuse the same seen set
Set<std::string> localKeys{{}}; Set<std::optional<std::string>> localKeys{std::nullopt};
// we can skip to the next class if this one is a top type // we can skip to the next class if this one is a top type
if (!computeKeysOf(*externTypeIter, localKeys, seen, isRaw, ctx)) if (!computeKeysOf(*externTypeIter, localKeys, seen, isRaw, ctx))
@ -2969,7 +3040,7 @@ TypeFunctionReductionResult<TypeId> keyofFunctionImpl(
{ {
seen.clear(); // we'll reuse the same seen set seen.clear(); // we'll reuse the same seen set
Set<std::string> localKeys{{}}; Set<std::optional<std::string>> localKeys{std::nullopt};
// we can skip to the next table if this one is the top table type // we can skip to the next table if this one is the top table type
if (!computeKeysOf(*tablesIter, localKeys, seen, isRaw, ctx)) if (!computeKeysOf(*tablesIter, localKeys, seen, isRaw, ctx))
@ -2992,6 +3063,105 @@ TypeFunctionReductionResult<TypeId> keyofFunctionImpl(
std::vector<TypeId> singletons; std::vector<TypeId> singletons;
singletons.reserve(keys.size()); singletons.reserve(keys.size());
for (const auto& key : keys)
{
if (key)
singletons.push_back(ctx->arena->addType(SingletonType{StringSingleton{*key}}));
}
// If there's only one entry, we don't need a UnionType.
// We can take straight take it from the first entry
// because it was added into the type arena already.
if (singletons.size() == 1)
return {singletons.front(), Reduction::MaybeOk, {}, {}};
return {ctx->arena->addType(UnionType{std::move(singletons)}), Reduction::MaybeOk, {}, {}};
}
else
{
// we're going to collect the keys in here
Set<std::string> keys{{}};
// computing the keys for extern types
if (normTy->hasExternTypes())
{
LUAU_ASSERT(!normTy->hasTables());
// seen set for key computation for extern types
DenseHashSet<TypeId> seen{{}};
auto externTypeIter = normTy->externTypes.ordering.begin();
auto externTypeIterEnd = normTy->externTypes.ordering.end();
LUAU_ASSERT(externTypeIter != externTypeIterEnd); // should be guaranteed by the `hasExternTypes` check earlier
// collect all the properties from the first class type
if (!computeKeysOf_DEPRECATED(*externTypeIter, keys, seen, isRaw, ctx))
return {ctx->builtins->stringType, Reduction::MaybeOk, {}, {}}; // if it failed, we have a top type!
// we need to look at each class to remove any keys that are not common amongst them all
while (++externTypeIter != externTypeIterEnd)
{
seen.clear(); // we'll reuse the same seen set
Set<std::string> localKeys{{}};
// we can skip to the next class if this one is a top type
if (!computeKeysOf_DEPRECATED(*externTypeIter, localKeys, seen, isRaw, ctx))
continue;
for (auto& key : keys)
{
// remove any keys that are not present in each class
if (!localKeys.contains(key))
keys.erase(key);
}
}
}
// computing the keys for tables
if (normTy->hasTables())
{
LUAU_ASSERT(!normTy->hasExternTypes());
// seen set for key computation for tables
DenseHashSet<TypeId> seen{{}};
auto tablesIter = normTy->tables.begin();
LUAU_ASSERT(tablesIter != normTy->tables.end()); // should be guaranteed by the `hasTables` check earlier
// collect all the properties from the first table type
if (!computeKeysOf_DEPRECATED(*tablesIter, keys, seen, isRaw, ctx))
return {ctx->builtins->stringType, Reduction::MaybeOk, {}, {}}; // if it failed, we have the top table type!
// we need to look at each tables to remove any keys that are not common amongst them all
while (++tablesIter != normTy->tables.end())
{
seen.clear(); // we'll reuse the same seen set
Set<std::string> localKeys{{}};
// we can skip to the next table if this one is the top table type
if (!computeKeysOf_DEPRECATED(*tablesIter, localKeys, seen, isRaw, ctx))
continue;
for (auto& key : keys)
{
// remove any keys that are not present in each table
if (!localKeys.contains(key))
keys.erase(key);
}
}
}
// if the set of keys is empty, `keyof<T>` is `never`
if (keys.empty())
return {ctx->builtins->neverType, Reduction::MaybeOk, {}, {}};
// everything is validated, we need only construct our big union of singletons now!
std::vector<TypeId> singletons;
singletons.reserve(keys.size());
for (const std::string& key : keys) for (const std::string& key : keys)
singletons.push_back(ctx->arena->addType(SingletonType{StringSingleton{key}})); singletons.push_back(ctx->arena->addType(SingletonType{StringSingleton{key}}));
@ -3003,6 +3173,7 @@ TypeFunctionReductionResult<TypeId> keyofFunctionImpl(
return {ctx->arena->addType(UnionType{std::move(singletons)}), Reduction::MaybeOk, {}, {}}; return {ctx->arena->addType(UnionType{std::move(singletons)}), Reduction::MaybeOk, {}, {}};
} }
}
TypeFunctionReductionResult<TypeId> keyofTypeFunction( TypeFunctionReductionResult<TypeId> keyofTypeFunction(
TypeId instance, TypeId instance,
@ -3054,7 +3225,21 @@ bool searchPropsAndIndexer(
{ {
if (tblProps.find(stringSingleton->value) != tblProps.end()) if (tblProps.find(stringSingleton->value) != tblProps.end())
{ {
TypeId propTy = follow(tblProps.at(stringSingleton->value).type());
TypeId propTy;
if (FFlag::LuauRemoveTypeCallsForReadWriteProps)
{
Property& prop = tblProps.at(stringSingleton->value);
if (prop.readTy)
propTy = follow(*prop.readTy);
else if (prop.writeTy)
propTy = follow(*prop.writeTy);
else // found the property, but there was no type associated with it
return false;
}
else
propTy = follow(tblProps.at(stringSingleton->value).type_DEPRECATED());
// property is a union type -> we need to extend our reduction type // property is a union type -> we need to extend our reduction type
if (auto propUnionTy = get<UnionType>(propTy)) if (auto propUnionTy = get<UnionType>(propTy))

View file

@ -20,6 +20,8 @@
// currently, controls serialization, deserialization, and `type.copy` // currently, controls serialization, deserialization, and `type.copy`
LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFunctionSerdeIterationLimit, 100'000); LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFunctionSerdeIterationLimit, 100'000);
LUAU_FASTFLAGVARIABLE(LuauTypeFunctionSerializeFollowMetatable)
namespace Luau namespace Luau
{ {
@ -388,7 +390,7 @@ private:
void serializeChildren(const MetatableType* m1, TypeFunctionTableType* m2) void serializeChildren(const MetatableType* m1, TypeFunctionTableType* m2)
{ {
// Serialize main part of the metatable immediately // Serialize main part of the metatable immediately
if (auto tableTy = get<TableType>(m1->table)) if (auto tableTy = get<TableType>(FFlag::LuauTypeFunctionSerializeFollowMetatable ? follow(m1->table) : m1->table))
serializeChildren(tableTy, m2); serializeChildren(tableTy, m2);
m2->metatable = shallowSerialize(m1->metatable); m2->metatable = shallowSerialize(m1->metatable);

View file

@ -32,7 +32,6 @@ LUAU_FASTINTVARIABLE(LuauVisitRecursionLimit, 500)
LUAU_FASTFLAG(LuauKnowsTheDataModel3) LUAU_FASTFLAG(LuauKnowsTheDataModel3)
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification) LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification)
LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
LUAU_FASTFLAGVARIABLE(LuauReduceCheckBinaryExprStackPressure) LUAU_FASTFLAGVARIABLE(LuauReduceCheckBinaryExprStackPressure)
@ -1773,7 +1772,7 @@ ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatDeclareExtern
else else
{ {
Luau::Property& prop = assignTo[propName]; Luau::Property& prop = assignTo[propName];
TypeId currentTy = prop.type(); TypeId currentTy = prop.type_DEPRECATED();
// We special-case this logic to keep the intersection flat; otherwise we // We special-case this logic to keep the intersection flat; otherwise we
// would create a ton of nested intersection types. // would create a ton of nested intersection types.
@ -1834,8 +1833,7 @@ ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatDeclareFuncti
); );
TypePackId argPack = resolveTypePack(funScope, global.params); TypePackId argPack = resolveTypePack(funScope, global.params);
TypePackId retPack = TypePackId retPack = resolveTypePack(funScope, *global.retTypes);
FFlag::LuauStoreReturnTypesAsPackOnAst ? resolveTypePack(funScope, *global.retTypes) : resolveTypePack(funScope, global.retTypes_DEPRECATED);
FunctionDefinition defn; FunctionDefinition defn;
@ -2096,7 +2094,7 @@ std::optional<TypeId> TypeChecker::getIndexTypeFromTypeImpl(
if (TableType* tableType = getMutableTableType(type)) if (TableType* tableType = getMutableTableType(type))
{ {
if (auto it = tableType->props.find(name); it != tableType->props.end()) if (auto it = tableType->props.find(name); it != tableType->props.end())
return it->second.type(); return it->second.type_DEPRECATED();
else if (auto indexer = tableType->indexer) else if (auto indexer = tableType->indexer)
{ {
// TODO: Property lookup should work with string singletons or unions thereof as the indexer key type. // TODO: Property lookup should work with string singletons or unions thereof as the indexer key type.
@ -2124,7 +2122,7 @@ std::optional<TypeId> TypeChecker::getIndexTypeFromTypeImpl(
{ {
const Property* prop = lookupExternTypeProp(cls, name); const Property* prop = lookupExternTypeProp(cls, name);
if (prop) if (prop)
return prop->type(); return prop->type_DEPRECATED();
if (auto indexer = cls->indexer) if (auto indexer = cls->indexer)
{ {
@ -2332,9 +2330,9 @@ TypeId TypeChecker::checkExprTable(
if (it != expectedTable->props.end()) if (it != expectedTable->props.end())
{ {
Property expectedProp = it->second; Property expectedProp = it->second;
ErrorVec errors = tryUnify(exprType, expectedProp.type(), scope, k->location); ErrorVec errors = tryUnify(exprType, expectedProp.type_DEPRECATED(), scope, k->location);
if (errors.empty()) if (errors.empty())
exprType = expectedProp.type(); exprType = expectedProp.type_DEPRECATED();
} }
else if (expectedTable->indexer && maybeString(expectedTable->indexer->indexType)) else if (expectedTable->indexer && maybeString(expectedTable->indexer->indexType))
{ {
@ -2428,7 +2426,7 @@ WithPredicate<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExp
if (expectedTable) if (expectedTable)
{ {
if (auto prop = expectedTable->props.find(key->value.data); prop != expectedTable->props.end()) if (auto prop = expectedTable->props.find(key->value.data); prop != expectedTable->props.end())
expectedResultType = prop->second.type(); expectedResultType = prop->second.type_DEPRECATED();
else if (expectedIndexType && maybeString(*expectedIndexType)) else if (expectedIndexType && maybeString(*expectedIndexType))
expectedResultType = expectedIndexResultType; expectedResultType = expectedIndexResultType;
} }
@ -2440,7 +2438,7 @@ WithPredicate<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExp
if (const TableType* ttv = get<TableType>(follow(expectedOption))) if (const TableType* ttv = get<TableType>(follow(expectedOption)))
{ {
if (auto prop = ttv->props.find(key->value.data); prop != ttv->props.end()) if (auto prop = ttv->props.find(key->value.data); prop != ttv->props.end())
expectedResultTypes.push_back(prop->second.type()); expectedResultTypes.push_back(prop->second.type_DEPRECATED());
else if (ttv->indexer && maybeString(ttv->indexer->indexType)) else if (ttv->indexer && maybeString(ttv->indexer->indexType))
expectedResultTypes.push_back(ttv->indexer->indexResultType); expectedResultTypes.push_back(ttv->indexer->indexResultType);
} }
@ -3408,7 +3406,7 @@ TypeId TypeChecker::checkLValueBinding(const ScopePtr& scope, const AstExprIndex
const auto& it = lhsTable->props.find(name); const auto& it = lhsTable->props.find(name);
if (it != lhsTable->props.end()) if (it != lhsTable->props.end())
{ {
return it->second.type(); return it->second.type_DEPRECATED();
} }
else if ((ctx == ValueContext::LValue && lhsTable->state == TableState::Unsealed) || lhsTable->state == TableState::Free) else if ((ctx == ValueContext::LValue && lhsTable->state == TableState::Unsealed) || lhsTable->state == TableState::Free)
{ {
@ -3449,7 +3447,7 @@ TypeId TypeChecker::checkLValueBinding(const ScopePtr& scope, const AstExprIndex
{ {
if (const Property* prop = lookupExternTypeProp(lhsExternType, name)) if (const Property* prop = lookupExternTypeProp(lhsExternType, name))
{ {
return prop->type(); return prop->type_DEPRECATED();
} }
if (auto indexer = lhsExternType->indexer) if (auto indexer = lhsExternType->indexer)
@ -3508,7 +3506,7 @@ TypeId TypeChecker::checkLValueBinding(const ScopePtr& scope, const AstExprIndex
{ {
if (const Property* prop = lookupExternTypeProp(exprExternType, value->value.data)) if (const Property* prop = lookupExternTypeProp(exprExternType, value->value.data))
{ {
return prop->type(); return prop->type_DEPRECATED();
} }
if (auto indexer = exprExternType->indexer) if (auto indexer = exprExternType->indexer)
@ -3618,7 +3616,7 @@ TypeId TypeChecker::checkLValueBinding(const ScopePtr& scope, const AstExprIndex
const auto& it = table->props.find(value->value.data); const auto& it = table->props.find(value->value.data);
if (it != table->props.end()) if (it != table->props.end())
{ {
propTypes.insert(it->second.type()); propTypes.insert(it->second.type_DEPRECATED());
} }
else if ((ctx == ValueContext::LValue && table->state == TableState::Unsealed) || table->state == TableState::Free) else if ((ctx == ValueContext::LValue && table->state == TableState::Unsealed) || table->state == TableState::Free)
{ {
@ -3759,12 +3757,12 @@ TypeId TypeChecker::checkFunctionName(const ScopePtr& scope, AstExpr& funName, T
Name name = indexName->index.value; Name name = indexName->index.value;
if (ttv->props.count(name)) if (ttv->props.count(name))
return ttv->props[name].type(); return ttv->props[name].type_DEPRECATED();
Property& property = ttv->props[name]; Property& property = ttv->props[name];
property.setType(freshTy()); property.setType(freshTy());
property.location = indexName->indexLocation; property.location = indexName->indexLocation;
return property.type(); return property.type_DEPRECATED();
} }
else if (funName.is<AstExprError>()) else if (funName.is<AstExprError>())
return errorRecoveryType(scope); return errorRecoveryType(scope);
@ -3831,10 +3829,8 @@ std::pair<TypeId, ScopePtr> TypeChecker::checkFunctionSignature(
auto [generics, genericPacks] = createGenericTypes(funScope, std::nullopt, expr, expr.generics, expr.genericPacks); auto [generics, genericPacks] = createGenericTypes(funScope, std::nullopt, expr, expr.generics, expr.genericPacks);
TypePackId retPack; TypePackId retPack;
if (FFlag::LuauStoreReturnTypesAsPackOnAst && expr.returnAnnotation) if (expr.returnAnnotation)
retPack = resolveTypePack(funScope, *expr.returnAnnotation); retPack = resolveTypePack(funScope, *expr.returnAnnotation);
else if (!FFlag::LuauStoreReturnTypesAsPackOnAst && expr.returnAnnotation_DEPRECATED)
retPack = resolveTypePack(funScope, *expr.returnAnnotation_DEPRECATED);
else if (isNonstrictMode()) else if (isNonstrictMode())
retPack = anyTypePack; retPack = anyTypePack;
else if (expectedFunctionType && expectedFunctionType->generics.empty() && expectedFunctionType->genericPacks.empty()) else if (expectedFunctionType && expectedFunctionType->generics.empty() && expectedFunctionType->genericPacks.empty())
@ -4041,8 +4037,7 @@ void TypeChecker::checkFunctionBody(const ScopePtr& scope, TypeId ty, const AstE
// If we're in nonstrict mode we want to only report this missing return // If we're in nonstrict mode we want to only report this missing return
// statement if there are type annotations on the function. In strict mode // statement if there are type annotations on the function. In strict mode
// we report it regardless. // we report it regardless.
if (!isNonstrictMode() || if (!isNonstrictMode() || function.returnAnnotation != nullptr)
(FFlag::LuauStoreReturnTypesAsPackOnAst ? function.returnAnnotation != nullptr : function.returnAnnotation_DEPRECATED.has_value()))
{ {
reportError(getEndLocation(function), FunctionExitsWithoutReturning{funTy->retTypes}); reportError(getEndLocation(function), FunctionExitsWithoutReturning{funTy->retTypes});
} }
@ -5785,8 +5780,7 @@ TypeId TypeChecker::resolveTypeWorker(const ScopePtr& scope, const AstType& anno
auto [generics, genericPacks] = createGenericTypes(funcScope, std::nullopt, annotation, func->generics, func->genericPacks); auto [generics, genericPacks] = createGenericTypes(funcScope, std::nullopt, annotation, func->generics, func->genericPacks);
TypePackId argTypes = resolveTypePack(funcScope, func->argTypes); TypePackId argTypes = resolveTypePack(funcScope, func->argTypes);
TypePackId retTypes = FFlag::LuauStoreReturnTypesAsPackOnAst ? resolveTypePack(funcScope, *func->returnTypes) TypePackId retTypes = resolveTypePack(funcScope, *func->returnTypes);
: resolveTypePack(funcScope, func->returnTypes_DEPRECATED);
std::vector<TypeId> genericTys; std::vector<TypeId> genericTys;
genericTys.reserve(generics.size()); genericTys.reserve(generics.size());

View file

@ -344,7 +344,7 @@ struct TraversalState
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
maybeType = property.isRead ? prop->readTy : prop->writeTy; maybeType = property.isRead ? prop->readTy : prop->writeTy;
else else
maybeType = prop->type(); maybeType = prop->type_DEPRECATED();
if (maybeType) if (maybeType)
{ {

View file

@ -14,6 +14,7 @@
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauEagerGeneralization4)
LUAU_FASTFLAGVARIABLE(LuauErrorSuppressionTypeFunctionArgs) LUAU_FASTFLAGVARIABLE(LuauErrorSuppressionTypeFunctionArgs)
LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps)
namespace Luau namespace Luau
{ {
@ -75,7 +76,17 @@ std::optional<Property> findTableProperty(NotNull<BuiltinTypes> builtinTypes, Er
{ {
const auto& fit = itt->props.find(name); const auto& fit = itt->props.find(name);
if (fit != itt->props.end()) if (fit != itt->props.end())
return fit->second.type(); {
if (FFlag::LuauRemoveTypeCallsForReadWriteProps && FFlag::LuauSolverV2)
{
if (fit->second.readTy)
return fit->second.readTy;
else
return fit->second.writeTy;
}
else
return fit->second.type_DEPRECATED();
}
} }
else if (const auto& itf = get<FunctionType>(index)) else if (const auto& itf = get<FunctionType>(index))
{ {
@ -124,7 +135,17 @@ std::optional<TypeId> findMetatableEntry(
auto it = mtt->props.find(entry); auto it = mtt->props.find(entry);
if (it != mtt->props.end()) if (it != mtt->props.end())
return it->second.type(); {
if (FFlag::LuauRemoveTypeCallsForReadWriteProps && FFlag::LuauSolverV2)
{
if (it->second.readTy)
return it->second.readTy;
else
return it->second.writeTy;
}
else
return it->second.type_DEPRECATED();
}
else else
return std::nullopt; return std::nullopt;
} }
@ -168,7 +189,7 @@ std::optional<TypeId> findTablePropertyRespectingMeta(
} }
} }
else else
return it->second.type(); return it->second.type_DEPRECATED();
} }
} }
@ -187,7 +208,20 @@ std::optional<TypeId> findTablePropertyRespectingMeta(
{ {
const auto& fit = itt->props.find(name); const auto& fit = itt->props.find(name);
if (fit != itt->props.end()) if (fit != itt->props.end())
return fit->second.type(); {
if (FFlag::LuauRemoveTypeCallsForReadWriteProps && FFlag::LuauSolverV2)
{
switch (context)
{
case ValueContext::RValue:
return fit->second.readTy;
case ValueContext::LValue:
return fit->second.writeTy;
}
}
else
return fit->second.type_DEPRECATED();
}
} }
else if (const auto& itf = get<FunctionType>(index)) else if (const auto& itf = get<FunctionType>(index))
{ {
@ -703,6 +737,34 @@ AstExpr* unwrapGroup(AstExpr* expr)
return expr; return expr;
} }
bool isApproximatelyFalsyType(TypeId ty)
{
ty = follow(ty);
bool seenNil = false;
bool seenFalse = false;
if (auto ut = get<UnionType>(ty))
{
for (auto option : ut)
{
if (auto pt = get<PrimitiveType>(option); pt && pt->type == PrimitiveType::NilType)
seenNil = true;
else if (auto st = get<SingletonType>(option); st && st->variant == BooleanSingleton{false})
seenFalse = true;
else
return false;
}
}
return seenFalse && seenNil;
}
bool isApproximatelyTruthyType(TypeId ty)
{
ty = follow(ty);
if (auto nt = get<NegationType>(ty))
return isApproximatelyFalsyType(nt->ty);
return false;
}
} // namespace Luau } // namespace Luau

View file

@ -342,7 +342,7 @@ static std::optional<std::pair<Luau::Name, const SingletonType*>> getTableMatchT
{ {
for (auto&& [name, prop] : ttv->props) for (auto&& [name, prop] : ttv->props)
{ {
if (auto sing = get<SingletonType>(follow(prop.type()))) if (auto sing = get<SingletonType>(follow(prop.type_DEPRECATED())))
return {{name, sing}}; return {{name, sing}};
} }
} }
@ -1938,7 +1938,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection,
{ {
auto subIter = subTable->props.find(propName); auto subIter = subTable->props.find(propName);
if (subIter == subTable->props.end() && subTable->state == TableState::Unsealed && !isOptional(superProp.type())) if (subIter == subTable->props.end() && subTable->state == TableState::Unsealed && !isOptional(superProp.type_DEPRECATED()))
missingProperties.push_back(propName); missingProperties.push_back(propName);
} }
@ -1980,7 +1980,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection,
variance = Invariant; variance = Invariant;
std::unique_ptr<Unifier> innerState = makeChildUnifier(); std::unique_ptr<Unifier> innerState = makeChildUnifier();
innerState->tryUnify_(r->second.type(), prop.type()); innerState->tryUnify_(r->second.type_DEPRECATED(), prop.type_DEPRECATED());
checkChildUnifierTypeMismatch(innerState->errors, name, superTy, subTy); checkChildUnifierTypeMismatch(innerState->errors, name, superTy, subTy);
@ -1997,7 +1997,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection,
variance = Invariant; variance = Invariant;
std::unique_ptr<Unifier> innerState = makeChildUnifier(); std::unique_ptr<Unifier> innerState = makeChildUnifier();
innerState->tryUnify_(subTable->indexer->indexResultType, prop.type()); innerState->tryUnify_(subTable->indexer->indexResultType, prop.type_DEPRECATED());
checkChildUnifierTypeMismatch(innerState->errors, name, superTy, subTy); checkChildUnifierTypeMismatch(innerState->errors, name, superTy, subTy);
@ -2005,7 +2005,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection,
log.concat(std::move(innerState->log)); log.concat(std::move(innerState->log));
failure |= innerState->failure; failure |= innerState->failure;
} }
else if (subTable->state == TableState::Unsealed && isOptional(prop.type())) else if (subTable->state == TableState::Unsealed && isOptional(prop.type_DEPRECATED()))
// This is sound because unsealed table types are precise, so `{ p : T } <: { p : T, q : U? }` // This is sound because unsealed table types are precise, so `{ p : T } <: { p : T, q : U? }`
// since if `t : { p : T }` then we are guaranteed that `t.q` is `nil`. // since if `t : { p : T }` then we are guaranteed that `t.q` is `nil`.
// TODO: if the supertype is written to, the subtype may no longer be precise (alias analysis?) // TODO: if the supertype is written to, the subtype may no longer be precise (alias analysis?)
@ -2076,11 +2076,11 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection,
std::unique_ptr<Unifier> innerState = makeChildUnifier(); std::unique_ptr<Unifier> innerState = makeChildUnifier();
if (FFlag::LuauFixIndexerSubtypingOrdering) if (FFlag::LuauFixIndexerSubtypingOrdering)
innerState->tryUnify_(prop.type(), superTable->indexer->indexResultType); innerState->tryUnify_(prop.type_DEPRECATED(), superTable->indexer->indexResultType);
else else
{ {
// Incredibly, the old solver depends on this bug somehow. // Incredibly, the old solver depends on this bug somehow.
innerState->tryUnify_(superTable->indexer->indexResultType, prop.type()); innerState->tryUnify_(superTable->indexer->indexResultType, prop.type_DEPRECATED());
} }
checkChildUnifierTypeMismatch(innerState->errors, name, superTy, subTy); checkChildUnifierTypeMismatch(innerState->errors, name, superTy, subTy);
@ -2095,7 +2095,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection,
// TODO: file a JIRA // TODO: file a JIRA
// TODO: hopefully readonly/writeonly properties will fix this. // TODO: hopefully readonly/writeonly properties will fix this.
Property clone = prop; Property clone = prop;
clone.setType(deeplyOptional(clone.type())); clone.setType(deeplyOptional(clone.type_DEPRECATED()));
PendingType* pendingSuper = log.queue(superTy); PendingType* pendingSuper = log.queue(superTy);
TableType* pendingSuperTtv = getMutable<TableType>(pendingSuper); TableType* pendingSuperTtv = getMutable<TableType>(pendingSuper);
@ -2271,7 +2271,7 @@ void Unifier::tryUnifyScalarShape(TypeId subTy, TypeId superTy, bool reversed)
if (auto it = mttv->props.find("__index"); it != mttv->props.end()) if (auto it = mttv->props.find("__index"); it != mttv->props.end())
{ {
TypeId ty = it->second.type(); TypeId ty = it->second.type_DEPRECATED();
std::unique_ptr<Unifier> child = makeChildUnifier(); std::unique_ptr<Unifier> child = makeChildUnifier();
child->tryUnify_(ty, superTy); child->tryUnify_(ty, superTy);
@ -2323,7 +2323,7 @@ TypeId Unifier::deeplyOptional(TypeId ty, std::unordered_map<TypeId, TypeId> see
result = types->addType(*ttv); result = types->addType(*ttv);
TableType* resultTtv = getMutable<TableType>(result); TableType* resultTtv = getMutable<TableType>(result);
for (auto& [name, prop] : resultTtv->props) for (auto& [name, prop] : resultTtv->props)
prop.setType(deeplyOptional(prop.type(), seen)); prop.setType(deeplyOptional(prop.type_DEPRECATED(), seen));
return types->addType(UnionType{{builtinTypes->nilType, result}}); return types->addType(UnionType{{builtinTypes->nilType, result}});
} }
else else
@ -2442,7 +2442,7 @@ void Unifier::tryUnifyWithExternType(TypeId subTy, TypeId superTy, bool reversed
else else
{ {
std::unique_ptr<Unifier> innerState = makeChildUnifier(); std::unique_ptr<Unifier> innerState = makeChildUnifier();
innerState->tryUnify_(classProp->type(), prop.type()); innerState->tryUnify_(classProp->type_DEPRECATED(), prop.type_DEPRECATED());
checkChildUnifierTypeMismatch(innerState->errors, propName, reversed ? subTy : superTy, reversed ? superTy : subTy); checkChildUnifierTypeMismatch(innerState->errors, propName, reversed ? subTy : superTy, reversed ? superTy : subTy);
@ -2621,7 +2621,7 @@ static void tryUnifyWithAny(
else if (auto table = state.log.getMutable<TableType>(ty)) else if (auto table = state.log.getMutable<TableType>(ty))
{ {
for (const auto& [_name, prop] : table->props) for (const auto& [_name, prop] : table->props)
queue.push_back(prop.type()); queue.push_back(prop.type_DEPRECATED());
if (table->indexer) if (table->indexer)
{ {

View file

@ -20,10 +20,18 @@
LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAG(LuauEnableWriteOnlyProperties) LUAU_FASTFLAG(LuauEnableWriteOnlyProperties)
LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauEagerGeneralization4)
LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps)
LUAU_FASTFLAG(LuauRefineTablesWithReadType)
namespace Luau namespace Luau
{ {
static bool isOptionalOrFree(TypeId ty)
{
ty = follow(ty);
return isOptional(ty) || (get<FreeType>(ty) != nullptr);
}
static bool areCompatible(TypeId left, TypeId right) static bool areCompatible(TypeId left, TypeId right)
{ {
auto p = get2<TableType, TableType>(follow(left), follow(right)); auto p = get2<TableType, TableType>(follow(left), follow(right));
@ -43,14 +51,32 @@ static bool areCompatible(TypeId left, TypeId right)
// the right table is free (and therefore potentially has an indexer or // the right table is free (and therefore potentially has an indexer or
// a compatible property) // a compatible property)
if (FFlag::LuauRemoveTypeCallsForReadWriteProps || FFlag::LuauRefineTablesWithReadType)
{
if (rightTable->state == TableState::Free || rightTable->indexer.has_value())
return true;
if (leftProp.isReadOnly() || leftProp.isShared())
{
if (isOptionalOrFree(*leftProp.readTy))
return true;
}
// FIXME: Could this create an issue for write only / divergent properties?
return false;
}
else
{
LUAU_ASSERT(leftProp.isReadOnly() || leftProp.isShared()); LUAU_ASSERT(leftProp.isReadOnly() || leftProp.isShared());
const TypeId leftType = follow(leftProp.isReadOnly() ? *leftProp.readTy : leftProp.type()); const TypeId leftType = follow(leftProp.isReadOnly() ? *leftProp.readTy : leftProp.type_DEPRECATED());
if (isOptional(leftType) || get<FreeType>(leftType) || rightTable->state == TableState::Free || rightTable->indexer.has_value()) if (isOptional(leftType) || get<FreeType>(leftType) || rightTable->state == TableState::Free || rightTable->indexer.has_value())
return true; return true;
return false; return false;
}
}; };
for (const auto& [name, leftProp] : leftTable->props) for (const auto& [name, leftProp] : leftTable->props)
@ -426,13 +452,13 @@ bool Unifier2::unify(TableType* subTable, const TableType* superTable)
if (subProp.isReadOnly() && superProp.isReadOnly()) if (subProp.isReadOnly() && superProp.isReadOnly())
result &= unify(*subProp.readTy, *superPropOpt->second.readTy); result &= unify(*subProp.readTy, *superPropOpt->second.readTy);
else if (subProp.isReadOnly()) else if (subProp.isReadOnly())
result &= unify(*subProp.readTy, superProp.type()); result &= unify(*subProp.readTy, superProp.type_DEPRECATED());
else if (superProp.isReadOnly()) else if (superProp.isReadOnly())
result &= unify(subProp.type(), *superProp.readTy); result &= unify(subProp.type_DEPRECATED(), *superProp.readTy);
else else
{ {
result &= unify(subProp.type(), superProp.type()); result &= unify(subProp.type_DEPRECATED(), superProp.type_DEPRECATED());
result &= unify(superProp.type(), subProp.type()); result &= unify(superProp.type_DEPRECATED(), subProp.type_DEPRECATED());
} }
} }
} }

View file

@ -451,24 +451,6 @@ public:
const std::optional<Location>& argLocation = std::nullopt const std::optional<Location>& argLocation = std::nullopt
); );
// Clip with FFlagLuauStoreReturnTypesAsPackOnAst
AstExprFunction(
const Location& location,
const AstArray<AstAttr*>& attributes,
const AstArray<AstGenericType*>& generics,
const AstArray<AstGenericTypePack*>& genericPacks,
AstLocal* self,
const AstArray<AstLocal*>& args,
bool vararg,
const Location& varargLocation,
AstStatBlock* body,
size_t functionDepth,
const AstName& debugname,
const std::optional<AstTypeList>& returnAnnotation,
AstTypePack* varargAnnotation = nullptr,
const std::optional<Location>& argLocation = std::nullopt
);
void visit(AstVisitor* visitor) override; void visit(AstVisitor* visitor) override;
bool hasNativeAttribute() const; bool hasNativeAttribute() const;
@ -479,8 +461,6 @@ public:
AstArray<AstGenericTypePack*> genericPacks; AstArray<AstGenericTypePack*> genericPacks;
AstLocal* self; AstLocal* self;
AstArray<AstLocal*> args; AstArray<AstLocal*> args;
// Clip with FFlagLuauStoreReturnTypesAsPackOnAst
std::optional<AstTypeList> returnAnnotation_DEPRECATED;
AstTypePack* returnAnnotation = nullptr; AstTypePack* returnAnnotation = nullptr;
bool vararg = false; bool vararg = false;
Location varargLocation; Location varargLocation;
@ -949,7 +929,6 @@ class AstStatDeclareFunction : public AstStat
public: public:
LUAU_RTTI(AstStatDeclareFunction) LUAU_RTTI(AstStatDeclareFunction)
// Clip with FFlagLuauStoreReturnTypesAsPackOnAst
AstStatDeclareFunction( AstStatDeclareFunction(
const Location& location, const Location& location,
const AstName& name, const AstName& name,
@ -963,7 +942,6 @@ public:
AstTypePack* retTypes AstTypePack* retTypes
); );
// Clip with FFlagLuauStoreReturnTypesAsPackOnAst
AstStatDeclareFunction( AstStatDeclareFunction(
const Location& location, const Location& location,
const AstArray<AstAttr*>& attributes, const AstArray<AstAttr*>& attributes,
@ -978,36 +956,6 @@ public:
AstTypePack* retTypes AstTypePack* retTypes
); );
// Clip with FFlagLuauStoreReturnTypesAsPackOnAst
AstStatDeclareFunction(
const Location& location,
const AstName& name,
const Location& nameLocation,
const AstArray<AstGenericType*>& generics,
const AstArray<AstGenericTypePack*>& genericPacks,
const AstTypeList& params,
const AstArray<AstArgumentName>& paramNames,
bool vararg,
const Location& varargLocation,
const AstTypeList& retTypes
);
// Clip with FFlagLuauStoreReturnTypesAsPackOnAst
AstStatDeclareFunction(
const Location& location,
const AstArray<AstAttr*>& attributes,
const AstName& name,
const Location& nameLocation,
const AstArray<AstGenericType*>& generics,
const AstArray<AstGenericTypePack*>& genericPacks,
const AstTypeList& params,
const AstArray<AstArgumentName>& paramNames,
bool vararg,
const Location& varargLocation,
const AstTypeList& retTypes
);
void visit(AstVisitor* visitor) override; void visit(AstVisitor* visitor) override;
bool isCheckedFunction() const; bool isCheckedFunction() const;
@ -1023,8 +971,6 @@ public:
bool vararg = false; bool vararg = false;
Location varargLocation; Location varargLocation;
AstTypePack* retTypes; AstTypePack* retTypes;
// Clip with FFlagLuauStoreReturnTypesAsPackOnAst
AstTypeList retTypes_DEPRECATED;
}; };
struct AstDeclaredExternTypeProperty struct AstDeclaredExternTypeProperty
@ -1167,27 +1113,6 @@ public:
AstTypePack* returnTypes AstTypePack* returnTypes
); );
// Clip with FFlagLuauStoreReturnTypesAsPackOnAst
AstTypeFunction(
const Location& location,
const AstArray<AstGenericType*>& generics,
const AstArray<AstGenericTypePack*>& genericPacks,
const AstTypeList& argTypes,
const AstArray<std::optional<AstArgumentName>>& argNames,
const AstTypeList& returnTypes
);
// Clip with FFlagLuauStoreReturnTypesAsPackOnAst
AstTypeFunction(
const Location& location,
const AstArray<AstAttr*>& attributes,
const AstArray<AstGenericType*>& generics,
const AstArray<AstGenericTypePack*>& genericPacks,
const AstTypeList& argTypes,
const AstArray<std::optional<AstArgumentName>>& argNames,
const AstTypeList& returnTypes
);
void visit(AstVisitor* visitor) override; void visit(AstVisitor* visitor) override;
bool isCheckedFunction() const; bool isCheckedFunction() const;
@ -1198,8 +1123,6 @@ public:
AstArray<AstGenericTypePack*> genericPacks; AstArray<AstGenericTypePack*> genericPacks;
AstTypeList argTypes; AstTypeList argTypes;
AstArray<std::optional<AstArgumentName>> argNames; AstArray<std::optional<AstArgumentName>> argNames;
// Clip with FFlagLuauStoreReturnTypesAsPackOnAst
AstTypeList returnTypes_DEPRECATED;
AstTypePack* returnTypes; AstTypePack* returnTypes;
}; };

View file

@ -15,7 +15,7 @@ class CstNode;
class ParseError : public std::exception class ParseError : public std::exception
{ {
public: public:
ParseError(const Location& location, const std::string& message); ParseError(const Location& location, std::string message);
virtual const char* what() const throw(); virtual const char* what() const throw();

View file

@ -183,14 +183,6 @@ private:
const Name* localName, const Name* localName,
const AstArray<AstAttr*>& attributes const AstArray<AstAttr*>& attributes
); );
// Clip with FFlagLuauStoreReturnTypesAsPackOnAst
std::pair<AstExprFunction*, AstLocal*> parseFunctionBody_DEPRECATED(
bool hasself,
const Lexeme& matchFunction,
const AstName& debugname,
const Name* localName,
const AstArray<AstAttr*>& attributes
);
// explist ::= {exp `,'} exp // explist ::= {exp `,'} exp
void parseExprList(TempVector<AstExpr*>& result, TempVector<Position>* commaPositions = nullptr); void parseExprList(TempVector<AstExpr*>& result, TempVector<Position>* commaPositions = nullptr);
@ -233,10 +225,6 @@ private:
AstTypePack* parseOptionalReturnType(Position* returnSpecifierPosition = nullptr); AstTypePack* parseOptionalReturnType(Position* returnSpecifierPosition = nullptr);
AstTypePack* parseReturnType(); AstTypePack* parseReturnType();
// Clip with FFlagLuauStoreReturnTypesAsPackOnAst
std::optional<AstTypeList> parseOptionalReturnType_DEPRECATED(Position* returnSpecifierPosition = nullptr);
std::pair<Location, AstTypeList> parseReturnType_DEPRECATED();
struct TableIndexerResult struct TableIndexerResult
{ {
AstTableIndexer* node; AstTableIndexer* node;
@ -246,8 +234,6 @@ private:
}; };
TableIndexerResult parseTableIndexer(AstTableAccess access, std::optional<Location> accessLocation, Lexeme begin); TableIndexerResult parseTableIndexer(AstTableAccess access, std::optional<Location> accessLocation, Lexeme begin);
// Remove with FFlagLuauStoreCSTData2
AstTableIndexer* parseTableIndexer_DEPRECATED(AstTableAccess access, std::optional<Location> accessLocation, Lexeme begin);
AstTypeOrPack parseFunctionType(bool allowPack, const AstArray<AstAttr*>& attributes); AstTypeOrPack parseFunctionType(bool allowPack, const AstArray<AstAttr*>& attributes);
AstType* parseFunctionTypeTail( AstType* parseFunctionTypeTail(

View file

@ -3,8 +3,6 @@
#include "Luau/Common.h" #include "Luau/Common.h"
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
namespace Luau namespace Luau
{ {
@ -258,41 +256,6 @@ AstExprFunction::AstExprFunction(
, debugname(debugname) , debugname(debugname)
, argLocation(argLocation) , argLocation(argLocation)
{ {
LUAU_ASSERT(FFlag::LuauStoreReturnTypesAsPackOnAst);
}
AstExprFunction::AstExprFunction(
const Location& location,
const AstArray<AstAttr*>& attributes,
const AstArray<AstGenericType*>& generics,
const AstArray<AstGenericTypePack*>& genericPacks,
AstLocal* self,
const AstArray<AstLocal*>& args,
bool vararg,
const Location& varargLocation,
AstStatBlock* body,
size_t functionDepth,
const AstName& debugname,
const std::optional<AstTypeList>& returnAnnotation,
AstTypePack* varargAnnotation,
const std::optional<Location>& argLocation
)
: AstExpr(ClassIndex(), location)
, attributes(attributes)
, generics(generics)
, genericPacks(genericPacks)
, self(self)
, args(args)
, returnAnnotation_DEPRECATED(returnAnnotation)
, vararg(vararg)
, varargLocation(varargLocation)
, varargAnnotation(varargAnnotation)
, body(body)
, functionDepth(functionDepth)
, debugname(debugname)
, argLocation(argLocation)
{
LUAU_ASSERT(!FFlag::LuauStoreReturnTypesAsPackOnAst);
} }
void AstExprFunction::visit(AstVisitor* visitor) void AstExprFunction::visit(AstVisitor* visitor)
@ -308,16 +271,8 @@ void AstExprFunction::visit(AstVisitor* visitor)
if (varargAnnotation) if (varargAnnotation)
varargAnnotation->visit(visitor); varargAnnotation->visit(visitor);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
if (returnAnnotation) if (returnAnnotation)
returnAnnotation->visit(visitor); returnAnnotation->visit(visitor);
}
else
{
if (returnAnnotation_DEPRECATED)
visitTypeList(visitor, *returnAnnotation_DEPRECATED);
}
body->visit(visitor); body->visit(visitor);
} }
@ -908,7 +863,6 @@ AstStatDeclareFunction::AstStatDeclareFunction(
, varargLocation(varargLocation) , varargLocation(varargLocation)
, retTypes(retTypes) , retTypes(retTypes)
{ {
LUAU_ASSERT(FFlag::LuauStoreReturnTypesAsPackOnAst);
} }
AstStatDeclareFunction::AstStatDeclareFunction( AstStatDeclareFunction::AstStatDeclareFunction(
@ -936,64 +890,6 @@ AstStatDeclareFunction::AstStatDeclareFunction(
, varargLocation(varargLocation) , varargLocation(varargLocation)
, retTypes(retTypes) , retTypes(retTypes)
{ {
LUAU_ASSERT(FFlag::LuauStoreReturnTypesAsPackOnAst);
}
// Clip with FFlagLuauStoreReturnTypesAsPackOnAst
AstStatDeclareFunction::AstStatDeclareFunction(
const Location& location,
const AstName& name,
const Location& nameLocation,
const AstArray<AstGenericType*>& generics,
const AstArray<AstGenericTypePack*>& genericPacks,
const AstTypeList& params,
const AstArray<AstArgumentName>& paramNames,
bool vararg,
const Location& varargLocation,
const AstTypeList& retTypes
)
: AstStat(ClassIndex(), location)
, attributes()
, name(name)
, nameLocation(nameLocation)
, generics(generics)
, genericPacks(genericPacks)
, params(params)
, paramNames(paramNames)
, vararg(vararg)
, varargLocation(varargLocation)
, retTypes_DEPRECATED(retTypes)
{
LUAU_ASSERT(!FFlag::LuauStoreReturnTypesAsPackOnAst);
}
// Clip with FFlagLuauStoreReturnTypesAsPackOnAst
AstStatDeclareFunction::AstStatDeclareFunction(
const Location& location,
const AstArray<AstAttr*>& attributes,
const AstName& name,
const Location& nameLocation,
const AstArray<AstGenericType*>& generics,
const AstArray<AstGenericTypePack*>& genericPacks,
const AstTypeList& params,
const AstArray<AstArgumentName>& paramNames,
bool vararg,
const Location& varargLocation,
const AstTypeList& retTypes
)
: AstStat(ClassIndex(), location)
, attributes(attributes)
, name(name)
, nameLocation(nameLocation)
, generics(generics)
, genericPacks(genericPacks)
, params(params)
, paramNames(paramNames)
, vararg(vararg)
, varargLocation(varargLocation)
, retTypes_DEPRECATED(retTypes)
{
LUAU_ASSERT(!FFlag::LuauStoreReturnTypesAsPackOnAst);
} }
void AstStatDeclareFunction::visit(AstVisitor* visitor) void AstStatDeclareFunction::visit(AstVisitor* visitor)
@ -1001,10 +897,7 @@ void AstStatDeclareFunction::visit(AstVisitor* visitor)
if (visitor->visit(this)) if (visitor->visit(this))
{ {
visitTypeList(visitor, params); visitTypeList(visitor, params);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
retTypes->visit(visitor); retTypes->visit(visitor);
else
visitTypeList(visitor, retTypes_DEPRECATED);
} }
} }
@ -1144,7 +1037,6 @@ AstTypeFunction::AstTypeFunction(
, argNames(argNames) , argNames(argNames)
, returnTypes(returnTypes) , returnTypes(returnTypes)
{ {
LUAU_ASSERT(FFlag::LuauStoreReturnTypesAsPackOnAst);
LUAU_ASSERT(argNames.size == 0 || argNames.size == argTypes.types.size); LUAU_ASSERT(argNames.size == 0 || argNames.size == argTypes.types.size);
} }
@ -1165,50 +1057,6 @@ AstTypeFunction::AstTypeFunction(
, argNames(argNames) , argNames(argNames)
, returnTypes(returnTypes) , returnTypes(returnTypes)
{ {
LUAU_ASSERT(FFlag::LuauStoreReturnTypesAsPackOnAst);
LUAU_ASSERT(argNames.size == 0 || argNames.size == argTypes.types.size);
}
// Clip with FFlagLuauStoreReturnTypesAsPackOnAst
AstTypeFunction::AstTypeFunction(
const Location& location,
const AstArray<AstGenericType*>& generics,
const AstArray<AstGenericTypePack*>& genericPacks,
const AstTypeList& argTypes,
const AstArray<std::optional<AstArgumentName>>& argNames,
const AstTypeList& returnTypes
)
: AstType(ClassIndex(), location)
, attributes()
, generics(generics)
, genericPacks(genericPacks)
, argTypes(argTypes)
, argNames(argNames)
, returnTypes_DEPRECATED(returnTypes)
{
LUAU_ASSERT(!FFlag::LuauStoreReturnTypesAsPackOnAst);
LUAU_ASSERT(argNames.size == 0 || argNames.size == argTypes.types.size);
}
// Clip with FFlagLuauStoreReturnTypesAsPackOnAst
AstTypeFunction::AstTypeFunction(
const Location& location,
const AstArray<AstAttr*>& attributes,
const AstArray<AstGenericType*>& generics,
const AstArray<AstGenericTypePack*>& genericPacks,
const AstTypeList& argTypes,
const AstArray<std::optional<AstArgumentName>>& argNames,
const AstTypeList& returnTypes
)
: AstType(ClassIndex(), location)
, attributes(attributes)
, generics(generics)
, genericPacks(genericPacks)
, argTypes(argTypes)
, argNames(argNames)
, returnTypes_DEPRECATED(returnTypes)
{
LUAU_ASSERT(!FFlag::LuauStoreReturnTypesAsPackOnAst);
LUAU_ASSERT(argNames.size == 0 || argNames.size == argTypes.types.size); LUAU_ASSERT(argNames.size == 0 || argNames.size == argTypes.types.size);
} }
@ -1217,10 +1065,7 @@ void AstTypeFunction::visit(AstVisitor* visitor)
if (visitor->visit(this)) if (visitor->visit(this))
{ {
visitTypeList(visitor, argTypes); visitTypeList(visitor, argTypes);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
returnTypes->visit(visitor); returnTypes->visit(visitor);
else
visitTypeList(visitor, returnTypes_DEPRECATED);
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -18,6 +18,7 @@ inline bool isAnalysisFlagExperimental(const char* flag)
"LuauTableCloneClonesType3", // requires fixes in lua-apps code, terrifyingly "LuauTableCloneClonesType3", // requires fixes in lua-apps code, terrifyingly
"LuauNormalizationReorderFreeTypeIntersect", // requires fixes in lua-apps code, also terrifyingly "LuauNormalizationReorderFreeTypeIntersect", // requires fixes in lua-apps code, also terrifyingly
"LuauSolverV2", "LuauSolverV2",
"UseNewLuauTypeSolverDefaultEnabled", // This can change the default solver used in cli applications, so it also needs to be disabled. Will require fixes in lua-apps code
// makes sure we always have at least one entry // makes sure we always have at least one entry
nullptr, nullptr,
}; };

View file

@ -73,7 +73,7 @@ struct CompileOptions
class CompileError : public std::exception class CompileError : public std::exception
{ {
public: public:
CompileError(const Location& location, const std::string& message); CompileError(const Location& location, std::string message);
virtual ~CompileError() throw(); virtual ~CompileError() throw();

View file

@ -26,9 +26,9 @@ LUAU_FASTINTVARIABLE(LuauCompileInlineThreshold, 25)
LUAU_FASTINTVARIABLE(LuauCompileInlineThresholdMaxBoost, 300) LUAU_FASTINTVARIABLE(LuauCompileInlineThresholdMaxBoost, 300)
LUAU_FASTINTVARIABLE(LuauCompileInlineDepth, 5) LUAU_FASTINTVARIABLE(LuauCompileInlineDepth, 5)
LUAU_FASTFLAGVARIABLE(LuauCompileInlineNonConstInit)
LUAU_FASTFLAGVARIABLE(LuauSeparateCompilerTypeInfo) LUAU_FASTFLAGVARIABLE(LuauSeparateCompilerTypeInfo)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
LUAU_FASTFLAGVARIABLE(LuauCompileFixTypeFunctionSkip) LUAU_FASTFLAGVARIABLE(LuauCompileFixTypeFunctionSkip)
namespace Luau namespace Luau
@ -45,9 +45,9 @@ static const uint8_t kInvalidReg = 255;
static const uint32_t kDefaultAllocPc = ~0u; static const uint32_t kDefaultAllocPc = ~0u;
CompileError::CompileError(const Location& location, const std::string& message) CompileError::CompileError(const Location& location, std::string message)
: location(location) : location(location)
, message(message) , message(std::move(message))
{ {
} }
@ -302,7 +302,7 @@ struct Compiler
{ {
f.canInline = true; f.canInline = true;
f.stackSize = stackSize; f.stackSize = stackSize;
f.costModel = modelCost(func->body, func->args.data, func->args.size, builtins); f.costModel = modelCost(func->body, func->args.data, func->args.size, builtins, constants);
// track functions that only ever return a single value so that we can convert multret calls to fixedret calls // track functions that only ever return a single value so that we can convert multret calls to fixedret calls
if (alwaysTerminates(func->body)) if (alwaysTerminates(func->body))
@ -696,6 +696,9 @@ struct Compiler
// if the argument is a local that isn't mutated, we will simply reuse the existing register // if the argument is a local that isn't mutated, we will simply reuse the existing register
if (int reg = le ? getExprLocalReg(le) : -1; reg >= 0 && (!lv || !lv->written)) if (int reg = le ? getExprLocalReg(le) : -1; reg >= 0 && (!lv || !lv->written))
{ {
if (FFlag::LuauCompileInlineNonConstInit)
args.push_back({var, uint8_t(reg), {Constant::Type_Unknown}, kDefaultAllocPc, lv ? lv->init : nullptr});
else
args.push_back({var, uint8_t(reg), {Constant::Type_Unknown}, kDefaultAllocPc}); args.push_back({var, uint8_t(reg), {Constant::Type_Unknown}, kDefaultAllocPc});
} }
else else
@ -719,10 +722,20 @@ struct Compiler
for (InlineArg& arg : args) for (InlineArg& arg : args)
{ {
if (arg.value.type == Constant::Type_Unknown) if (arg.value.type == Constant::Type_Unknown)
{
pushLocal(arg.local, arg.reg, arg.allocpc); pushLocal(arg.local, arg.reg, arg.allocpc);
if (FFlag::LuauCompileInlineNonConstInit && arg.init)
{
if (Variable* lv = variables.find(arg.local))
lv->init = arg.init;
}
}
else else
{
locstants[arg.local] = arg.value; locstants[arg.local] = arg.value;
} }
}
// the inline frame will be used to compile return statements as well as to reject recursive inlining attempts // the inline frame will be used to compile return statements as well as to reject recursive inlining attempts
inlineFrames.push_back({func, oldLocals, target, targetCount}); inlineFrames.push_back({func, oldLocals, target, targetCount});
@ -771,6 +784,12 @@ struct Compiler
if (Constant* var = locstants.find(local)) if (Constant* var = locstants.find(local))
var->type = Constant::Type_Unknown; var->type = Constant::Type_Unknown;
if (FFlag::LuauCompileInlineNonConstInit)
{
if (Variable* lv = variables.find(local))
lv->init = nullptr;
}
} }
foldConstants(constants, variables, locstants, builtinsFold, builtinsFoldLibraryK, options.libraryMemberConstantCb, func->body); foldConstants(constants, variables, locstants, builtinsFold, builtinsFoldLibraryK, options.libraryMemberConstantCb, func->body);
@ -3016,7 +3035,7 @@ struct Compiler
} }
AstLocal* var = stat->var; AstLocal* var = stat->var;
uint64_t costModel = modelCost(stat->body, &var, 1, builtins); uint64_t costModel = modelCost(stat->body, &var, 1, builtins, constants);
// we use a dynamic cost threshold that's based on the fixed limit boosted by the cost advantage we gain due to unrolling // we use a dynamic cost threshold that's based on the fixed limit boosted by the cost advantage we gain due to unrolling
bool varc = true; bool varc = true;
@ -4109,6 +4128,8 @@ struct Compiler
uint8_t reg; uint8_t reg;
Constant value; Constant value;
uint32_t allocpc; uint32_t allocpc;
AstExpr* init;
}; };
struct InlineFrame struct InlineFrame
@ -4325,8 +4346,6 @@ void compileOrThrow(BytecodeBuilder& bytecode, const ParseResult& parseResult, c
mainFlags |= LPF_NATIVE_FUNCTION; mainFlags |= LPF_NATIVE_FUNCTION;
} }
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
AstExprFunction main( AstExprFunction main(
root->location, root->location,
/* attributes= */ AstArray<AstAttr*>({nullptr, 0}), /* attributes= */ AstArray<AstAttr*>({nullptr, 0}),
@ -4349,31 +4368,6 @@ void compileOrThrow(BytecodeBuilder& bytecode, const ParseResult& parseResult, c
bytecode.setMainFunction(mainid); bytecode.setMainFunction(mainid);
bytecode.finalize(); bytecode.finalize();
} }
else
{
AstExprFunction main(
root->location,
/* attributes= */ AstArray<AstAttr*>({nullptr, 0}),
/* generics= */ AstArray<AstGenericType*>(),
/* genericPacks= */ AstArray<AstGenericTypePack*>(),
/* self= */ nullptr,
AstArray<AstLocal*>(),
/* vararg= */ true,
/* varargLocation= */ Luau::Location(),
root,
/* functionDepth= */ 0,
/* debugname= */ AstName(),
/* returnAnnotation= */ std::nullopt
);
uint32_t mainid = compiler.compileFunction(&main, mainFlags);
const Compiler::Function* mainf = compiler.functions.find(&main);
LUAU_ASSERT(mainf && mainf->upvals.empty());
bytecode.setMainFunction(mainid);
bytecode.finalize();
}
}
void compileOrThrow(BytecodeBuilder& bytecode, const std::string& source, const CompileOptions& options, const ParseOptions& parseOptions) void compileOrThrow(BytecodeBuilder& bytecode, const std::string& source, const CompileOptions& options, const ParseOptions& parseOptions)
{ {

View file

@ -4,8 +4,12 @@
#include "Luau/Common.h" #include "Luau/Common.h"
#include "Luau/DenseHash.h" #include "Luau/DenseHash.h"
#include "ConstantFolding.h"
#include <limits.h> #include <limits.h>
LUAU_FASTFLAGVARIABLE(LuauCompileCostModelConstants)
namespace Luau namespace Luau
{ {
namespace Compile namespace Compile
@ -39,7 +43,7 @@ static uint64_t parallelMulSat(uint64_t a, int b)
return r | (s - (s >> 7)); return r | (s - (s >> 7));
} }
inline bool getNumber(AstExpr* node, double& result) inline bool getNumber_DEPRECATED(AstExpr* node, double& result)
{ {
// since constant model doesn't use constant folding atm, we perform the basic extraction that's sufficient to handle positive/negative literals // since constant model doesn't use constant folding atm, we perform the basic extraction that's sufficient to handle positive/negative literals
if (AstExprConstantNumber* ne = node->as<AstExprConstantNumber>()) if (AstExprConstantNumber* ne = node->as<AstExprConstantNumber>())
@ -114,18 +118,23 @@ struct Cost
struct CostVisitor : AstVisitor struct CostVisitor : AstVisitor
{ {
const DenseHashMap<AstExprCall*, int>& builtins; const DenseHashMap<AstExprCall*, int>& builtins;
const DenseHashMap<AstExpr*, Constant>& constants;
DenseHashMap<AstLocal*, uint64_t> vars; DenseHashMap<AstLocal*, uint64_t> vars;
Cost result; Cost result;
CostVisitor(const DenseHashMap<AstExprCall*, int>& builtins) CostVisitor(const DenseHashMap<AstExprCall*, int>& builtins, const DenseHashMap<AstExpr*, Constant>& constants)
: builtins(builtins) : builtins(builtins)
, constants(constants)
, vars(nullptr) , vars(nullptr)
{ {
} }
Cost model(AstExpr* node) Cost model(AstExpr* node)
{ {
if (FFlag::LuauCompileCostModelConstants && constants.contains(node))
return Cost(0, Cost::kLiteral);
if (AstExprGroup* expr = node->as<AstExprGroup>()) if (AstExprGroup* expr = node->as<AstExprGroup>())
{ {
return model(expr->expr); return model(expr->expr);
@ -270,8 +279,18 @@ struct CostVisitor : AstVisitor
int tripCount = -1; int tripCount = -1;
double from, to, step = 1; double from, to, step = 1;
if (FFlag::LuauCompileCostModelConstants)
{
if (getNumber(node->from, from) && getNumber(node->to, to) && (!node->step || getNumber(node->step, step))) if (getNumber(node->from, from) && getNumber(node->to, to) && (!node->step || getNumber(node->step, step)))
tripCount = getTripCount(from, to, step); tripCount = getTripCount(from, to, step);
}
else
{
if (getNumber_DEPRECATED(node->from, from) && getNumber_DEPRECATED(node->to, to) &&
(!node->step || getNumber_DEPRECATED(node->step, step)))
tripCount = getTripCount(from, to, step);
}
loop(node->body, 1, tripCount < 0 ? 3 : tripCount); loop(node->body, 1, tripCount < 0 ? 3 : tripCount);
return false; return false;
@ -369,11 +388,31 @@ struct CostVisitor : AstVisitor
return false; return false;
} }
bool getNumber(AstExpr* node, double& result)
{
if (const Constant* constant = constants.find(node))
{
if (constant->type == Constant::Type_Number)
{
result = constant->valueNumber;
return true;
}
}
return false;
}
}; };
uint64_t modelCost(AstNode* root, AstLocal* const* vars, size_t varCount, const DenseHashMap<AstExprCall*, int>& builtins) uint64_t modelCost(
AstNode* root,
AstLocal* const* vars,
size_t varCount,
const DenseHashMap<AstExprCall*, int>& builtins,
const DenseHashMap<AstExpr*, Constant>& constants
)
{ {
CostVisitor visitor{builtins}; CostVisitor visitor{builtins, constants};
for (size_t i = 0; i < varCount && i < 7; ++i) for (size_t i = 0; i < varCount && i < 7; ++i)
visitor.vars[vars[i]] = 0xffull << (i * 8 + 8); visitor.vars[vars[i]] = 0xffull << (i * 8 + 8);
@ -382,6 +421,14 @@ uint64_t modelCost(AstNode* root, AstLocal* const* vars, size_t varCount, const
return visitor.result.model; return visitor.result.model;
} }
uint64_t modelCost(AstNode* root, AstLocal* const* vars, size_t varCount)
{
DenseHashMap<AstExprCall*, int> builtins{nullptr};
DenseHashMap<AstExpr*, Constant> constants{nullptr};
return modelCost(root, vars, varCount, builtins, constants);
}
int computeCost(uint64_t model, const bool* varsConst, size_t varCount) int computeCost(uint64_t model, const bool* varsConst, size_t varCount)
{ {
int cost = int(model & 0x7f); int cost = int(model & 0x7f);

View file

@ -9,8 +9,18 @@ namespace Luau
namespace Compile namespace Compile
{ {
struct Constant;
// cost model: 8 bytes, where first byte is the baseline cost, and the next 7 bytes are discounts for when variable #i is constant // cost model: 8 bytes, where first byte is the baseline cost, and the next 7 bytes are discounts for when variable #i is constant
uint64_t modelCost(AstNode* root, AstLocal* const* vars, size_t varCount, const DenseHashMap<AstExprCall*, int>& builtins); uint64_t modelCost(
AstNode* root,
AstLocal* const* vars,
size_t varCount,
const DenseHashMap<AstExprCall*, int>& builtins,
const DenseHashMap<AstExpr*, Constant>& constants
);
// when additional data is not available, used to test the cost model
uint64_t modelCost(AstNode* root, AstLocal* const* vars, size_t varCount);
// cost is computed as B - sum(Di * Ci), where B is baseline cost, Di is the discount for each variable and Ci is 1 when variable #i is constant // cost is computed as B - sum(Di * Ci), where B is baseline cost, Di is the discount for each variable and Ci is 1 when variable #i is constant
int computeCost(uint64_t model, const bool* varsConst, size_t varCount); int computeCost(uint64_t model, const bool* varsConst, size_t varCount);

View file

@ -3,6 +3,8 @@
#include "Luau/Lexer.h" #include "Luau/Lexer.h"
LUAU_FASTFLAG(LuauCompileInlineNonConstInit)
namespace Luau namespace Luau
{ {
namespace Compile namespace Compile
@ -80,6 +82,17 @@ struct ValueVisitor : AstVisitor
return false; return false;
} }
bool visit(AstExprFunction* node) override
{
if (FFlag::LuauCompileInlineNonConstInit)
{
for (AstLocal* arg : node->args)
variables[arg].init = nullptr;
}
return true;
}
}; };
void assignMutable(DenseHashMap<AstName, Global>& globals, const AstNameTable& names, const char* const* mutableGlobals) void assignMutable(DenseHashMap<AstName, Global>& globals, const AstNameTable& names, const char* const* mutableGlobals)

View file

@ -12,16 +12,11 @@
#include <string.h> #include <string.h>
#include <stdio.h> #include <stdio.h>
LUAU_FASTFLAGVARIABLE(LuauCurrentLineBounds)
static const char* getfuncname(Closure* f); static const char* getfuncname(Closure* f);
static int currentpc(lua_State* L, CallInfo* ci) static int currentpc(lua_State* L, CallInfo* ci)
{ {
if (FFlag::LuauCurrentLineBounds)
return pcRel(ci->savedpc, ci_func(ci)->l.p); return pcRel(ci->savedpc, ci_func(ci)->l.p);
else
return pcRel_DEPRECATED(ci->savedpc, ci_func(ci)->l.p);
} }
static int currentline(lua_State* L, CallInfo* ci) static int currentline(lua_State* L, CallInfo* ci)

View file

@ -5,8 +5,6 @@
#include "lstate.h" #include "lstate.h"
#define pcRel(pc, p) ((pc) && (pc) != (p)->code ? cast_to(int, (pc) - (p)->code) - 1 : 0) #define pcRel(pc, p) ((pc) && (pc) != (p)->code ? cast_to(int, (pc) - (p)->code) - 1 : 0)
// TODO: remove with FFlagLuauCurrentLineBounds
#define pcRel_DEPRECATED(pc, p) ((pc) ? cast_to(int, (pc) - (p)->code) - 1 : 0)
#define luaG_typeerror(L, o, opname) luaG_typeerrorL(L, o, opname) #define luaG_typeerror(L, o, opname) luaG_typeerrorL(L, o, opname)
#define luaG_forerror(L, o, what) luaG_forerrorL(L, o, what) #define luaG_forerror(L, o, what) luaG_forerrorL(L, o, what)

View file

@ -14,7 +14,6 @@
#include <string.h> #include <string.h>
#include <stdio.h> #include <stdio.h>
LUAU_FASTFLAG(LuauCurrentLineBounds)
LUAU_FASTFLAGVARIABLE(LuauHeapNameDetails) LUAU_FASTFLAGVARIABLE(LuauHeapNameDetails)
static void validateobjref(global_State* g, GCObject* f, GCObject* t) static void validateobjref(global_State* g, GCObject* f, GCObject* t)
@ -465,7 +464,7 @@ static void dumpthread(FILE* f, lua_State* th)
else if (isLua(ci)) else if (isLua(ci))
{ {
Proto* p = ci_func(ci)->l.p; Proto* p = ci_func(ci)->l.p;
int pc = FFlag::LuauCurrentLineBounds ? pcRel(ci->savedpc, p) : pcRel_DEPRECATED(ci->savedpc, p); int pc = pcRel(ci->savedpc, p);
const LocVar* var = luaF_findlocal(p, int(v - ci->base), pc); const LocVar* var = luaF_findlocal(p, int(v - ci->base), pc);
if (var && var->varname) if (var && var->varname)

View file

@ -16,8 +16,6 @@
#include <string.h> #include <string.h>
LUAU_FASTFLAG(LuauCurrentLineBounds)
// Disable c99-designator to avoid the warning in CGOTO dispatch table // Disable c99-designator to avoid the warning in CGOTO dispatch table
#ifdef __clang__ #ifdef __clang__
#if __has_warning("-Wc99-designator") #if __has_warning("-Wc99-designator")
@ -149,8 +147,6 @@ LUAU_NOINLINE void luau_callhook(lua_State* L, lua_Hook hook, void* userdata)
L->base = L->ci->base; L->base = L->ci->base;
} }
if (FFlag::LuauCurrentLineBounds)
{
Closure* cl = clvalue(L->ci->func); Closure* cl = clvalue(L->ci->func);
// note: the pc expectations of the hook are matching the general "pc points to next instruction" // note: the pc expectations of the hook are matching the general "pc points to next instruction"
@ -172,30 +168,6 @@ LUAU_NOINLINE void luau_callhook(lua_State* L, lua_Hook hook, void* userdata)
hook(L, &ar); hook(L, &ar);
L->ci->savedpc = oldsavedpc; L->ci->savedpc = oldsavedpc;
}
else
{
// note: the pc expectations of the hook are matching the general "pc points to next instruction"
// however, for the hook to be able to continue execution from the same point, this is called with savedpc at the *current* instruction
// this needs to be called before luaD_checkstack in case it fails to reallocate stack
if (L->ci->savedpc)
L->ci->savedpc++;
luaD_checkstack(L, LUA_MINSTACK); // ensure minimum stack size
L->ci->top = L->top + LUA_MINSTACK;
LUAU_ASSERT(L->ci->top <= L->stack_last);
Closure* cl = clvalue(L->ci->func);
lua_Debug ar;
ar.currentline = cl->isC ? -1 : luaG_getline(cl->l.p, pcRel(L->ci->savedpc, cl->l.p));
ar.userdata = userdata;
hook(L, &ar);
if (L->ci->savedpc)
L->ci->savedpc--;
}
L->ci->top = restorestack(L, ci_top); L->ci->top = restorestack(L, ci_top);
L->top = restorestack(L, top); L->top = restorestack(L, top);

View file

@ -13,8 +13,6 @@
#include <string.h> #include <string.h>
LUAU_FASTFLAGVARIABLE(LuauLoadNoOomThrow)
template<typename T> template<typename T>
struct TempBuffer struct TempBuffer
{ {
@ -27,15 +25,6 @@ struct TempBuffer
, data(NULL) , data(NULL)
, count(0) , count(0)
{ {
LUAU_ASSERT(FFlag::LuauLoadNoOomThrow);
}
TempBuffer(lua_State* L, size_t count)
: L(L)
, data(luaM_newarray(L, count, T, 0))
, count(count)
{
LUAU_ASSERT(!FFlag::LuauLoadNoOomThrow);
} }
TempBuffer(const TempBuffer&) = delete; TempBuffer(const TempBuffer&) = delete;
@ -614,362 +603,8 @@ static int loadsafe(
return 0; return 0;
} }
int luau_load_DEPRECATED(lua_State* L, const char* chunkname, const char* data, size_t size, int env)
{
size_t offset = 0;
uint8_t version = read<uint8_t>(data, size, offset);
// 0 means the rest of the bytecode is the error message
if (version == 0)
{
char chunkbuf[LUA_IDSIZE];
const char* chunkid = luaO_chunkid(chunkbuf, sizeof(chunkbuf), chunkname, strlen(chunkname));
lua_pushfstring(L, "%s%.*s", chunkid, int(size - offset), data + offset);
return 1;
}
if (version < LBC_VERSION_MIN || version > LBC_VERSION_MAX)
{
char chunkbuf[LUA_IDSIZE];
const char* chunkid = luaO_chunkid(chunkbuf, sizeof(chunkbuf), chunkname, strlen(chunkname));
lua_pushfstring(L, "%s: bytecode version mismatch (expected [%d..%d], got %d)", chunkid, LBC_VERSION_MIN, LBC_VERSION_MAX, version);
return 1;
}
// we will allocate a fair amount of memory so check GC before we do
luaC_checkGC(L);
// pause GC for the duration of deserialization - some objects we're creating aren't rooted
const ScopedSetGCThreshold pauseGC{L->global, SIZE_MAX};
// env is 0 for current environment and a stack index otherwise
LuaTable* envt = (env == 0) ? L->gt : hvalue(luaA_toobject(L, env));
TString* source = luaS_new(L, chunkname);
uint8_t typesversion = 0;
if (version >= 4)
{
typesversion = read<uint8_t>(data, size, offset);
if (typesversion < LBC_TYPE_VERSION_MIN || typesversion > LBC_TYPE_VERSION_MAX)
{
char chunkbuf[LUA_IDSIZE];
const char* chunkid = luaO_chunkid(chunkbuf, sizeof(chunkbuf), chunkname, strlen(chunkname));
lua_pushfstring(
L, "%s: bytecode type version mismatch (expected [%d..%d], got %d)", chunkid, LBC_TYPE_VERSION_MIN, LBC_TYPE_VERSION_MAX, typesversion
);
return 1;
}
}
// string table
unsigned int stringCount = readVarInt(data, size, offset);
TempBuffer<TString*> strings(L, stringCount);
for (unsigned int i = 0; i < stringCount; ++i)
{
unsigned int length = readVarInt(data, size, offset);
strings[i] = luaS_newlstr(L, data + offset, length);
offset += length;
}
// userdata type remapping table
// for unknown userdata types, the entry will remap to common 'userdata' type
const uint32_t userdataTypeLimit = LBC_TYPE_TAGGED_USERDATA_END - LBC_TYPE_TAGGED_USERDATA_BASE;
uint8_t userdataRemapping[userdataTypeLimit];
if (typesversion == 3)
{
memset(userdataRemapping, LBC_TYPE_USERDATA, userdataTypeLimit);
uint8_t index = read<uint8_t>(data, size, offset);
while (index != 0)
{
TString* name = readString(strings, data, size, offset);
if (uint32_t(index - 1) < userdataTypeLimit)
{
if (auto cb = L->global->ecb.gettypemapping)
userdataRemapping[index - 1] = cb(L, getstr(name), name->len);
}
index = read<uint8_t>(data, size, offset);
}
}
// proto table
unsigned int protoCount = readVarInt(data, size, offset);
TempBuffer<Proto*> protos(L, protoCount);
for (unsigned int i = 0; i < protoCount; ++i)
{
Proto* p = luaF_newproto(L);
p->source = source;
p->bytecodeid = int(i);
p->maxstacksize = read<uint8_t>(data, size, offset);
p->numparams = read<uint8_t>(data, size, offset);
p->nups = read<uint8_t>(data, size, offset);
p->is_vararg = read<uint8_t>(data, size, offset);
if (version >= 4)
{
p->flags = read<uint8_t>(data, size, offset);
if (typesversion == 1)
{
uint32_t typesize = readVarInt(data, size, offset);
if (typesize)
{
uint8_t* types = (uint8_t*)data + offset;
LUAU_ASSERT(typesize == unsigned(2 + p->numparams));
LUAU_ASSERT(types[0] == LBC_TYPE_FUNCTION);
LUAU_ASSERT(types[1] == p->numparams);
// transform v1 into v2 format
int headersize = typesize > 127 ? 4 : 3;
p->typeinfo = luaM_newarray(L, headersize + typesize, uint8_t, p->memcat);
p->sizetypeinfo = headersize + typesize;
if (headersize == 4)
{
p->typeinfo[0] = (typesize & 127) | (1 << 7);
p->typeinfo[1] = typesize >> 7;
p->typeinfo[2] = 0;
p->typeinfo[3] = 0;
}
else
{
p->typeinfo[0] = uint8_t(typesize);
p->typeinfo[1] = 0;
p->typeinfo[2] = 0;
}
memcpy(p->typeinfo + headersize, types, typesize);
}
offset += typesize;
}
else if (typesversion == 2 || typesversion == 3)
{
uint32_t typesize = readVarInt(data, size, offset);
if (typesize)
{
uint8_t* types = (uint8_t*)data + offset;
p->typeinfo = luaM_newarray(L, typesize, uint8_t, p->memcat);
p->sizetypeinfo = typesize;
memcpy(p->typeinfo, types, typesize);
offset += typesize;
if (typesversion == 3)
{
remapUserdataTypes((char*)(uint8_t*)p->typeinfo, p->sizetypeinfo, userdataRemapping, userdataTypeLimit);
}
}
}
}
const int sizecode = readVarInt(data, size, offset);
p->code = luaM_newarray(L, sizecode, Instruction, p->memcat);
p->sizecode = sizecode;
for (int j = 0; j < p->sizecode; ++j)
p->code[j] = read<uint32_t>(data, size, offset);
p->codeentry = p->code;
const int sizek = readVarInt(data, size, offset);
p->k = luaM_newarray(L, sizek, TValue, p->memcat);
p->sizek = sizek;
// Initialize the constants to nil to ensure they have a valid state
// in the event that some operation in the following loop fails with
// an exception.
for (int j = 0; j < p->sizek; ++j)
{
setnilvalue(&p->k[j]);
}
for (int j = 0; j < p->sizek; ++j)
{
switch (read<uint8_t>(data, size, offset))
{
case LBC_CONSTANT_NIL:
// All constants have already been pre-initialized to nil
break;
case LBC_CONSTANT_BOOLEAN:
{
uint8_t v = read<uint8_t>(data, size, offset);
setbvalue(&p->k[j], v);
break;
}
case LBC_CONSTANT_NUMBER:
{
double v = read<double>(data, size, offset);
setnvalue(&p->k[j], v);
break;
}
case LBC_CONSTANT_VECTOR:
{
float x = read<float>(data, size, offset);
float y = read<float>(data, size, offset);
float z = read<float>(data, size, offset);
float w = read<float>(data, size, offset);
(void)w;
setvvalue(&p->k[j], x, y, z, w);
break;
}
case LBC_CONSTANT_STRING:
{
TString* v = readString(strings, data, size, offset);
setsvalue(L, &p->k[j], v);
break;
}
case LBC_CONSTANT_IMPORT:
{
uint32_t iid = read<uint32_t>(data, size, offset);
resolveImportSafe(L, envt, p->k, iid);
setobj(L, &p->k[j], L->top - 1);
L->top--;
break;
}
case LBC_CONSTANT_TABLE:
{
int keys = readVarInt(data, size, offset);
LuaTable* h = luaH_new(L, 0, keys);
for (int i = 0; i < keys; ++i)
{
int key = readVarInt(data, size, offset);
TValue* val = luaH_set(L, h, &p->k[key]);
setnvalue(val, 0.0);
}
sethvalue(L, &p->k[j], h);
break;
}
case LBC_CONSTANT_CLOSURE:
{
uint32_t fid = readVarInt(data, size, offset);
Closure* cl = luaF_newLclosure(L, protos[fid]->nups, envt, protos[fid]);
cl->preload = (cl->nupvalues > 0);
setclvalue(L, &p->k[j], cl);
break;
}
default:
LUAU_ASSERT(!"Unexpected constant kind");
}
}
const int sizep = readVarInt(data, size, offset);
p->p = luaM_newarray(L, sizep, Proto*, p->memcat);
p->sizep = sizep;
for (int j = 0; j < p->sizep; ++j)
{
uint32_t fid = readVarInt(data, size, offset);
p->p[j] = protos[fid];
}
p->linedefined = readVarInt(data, size, offset);
p->debugname = readString(strings, data, size, offset);
uint8_t lineinfo = read<uint8_t>(data, size, offset);
if (lineinfo)
{
p->linegaplog2 = read<uint8_t>(data, size, offset);
int intervals = ((p->sizecode - 1) >> p->linegaplog2) + 1;
int absoffset = (p->sizecode + 3) & ~3;
const int sizelineinfo = absoffset + intervals * sizeof(int);
p->lineinfo = luaM_newarray(L, sizelineinfo, uint8_t, p->memcat);
p->sizelineinfo = sizelineinfo;
p->abslineinfo = (int*)(p->lineinfo + absoffset);
uint8_t lastoffset = 0;
for (int j = 0; j < p->sizecode; ++j)
{
lastoffset += read<uint8_t>(data, size, offset);
p->lineinfo[j] = lastoffset;
}
int lastline = 0;
for (int j = 0; j < intervals; ++j)
{
lastline += read<int32_t>(data, size, offset);
p->abslineinfo[j] = lastline;
}
}
uint8_t debuginfo = read<uint8_t>(data, size, offset);
if (debuginfo)
{
const int sizelocvars = readVarInt(data, size, offset);
p->locvars = luaM_newarray(L, sizelocvars, LocVar, p->memcat);
p->sizelocvars = sizelocvars;
for (int j = 0; j < p->sizelocvars; ++j)
{
p->locvars[j].varname = readString(strings, data, size, offset);
p->locvars[j].startpc = readVarInt(data, size, offset);
p->locvars[j].endpc = readVarInt(data, size, offset);
p->locvars[j].reg = read<uint8_t>(data, size, offset);
}
const int sizeupvalues = readVarInt(data, size, offset);
LUAU_ASSERT(sizeupvalues == p->nups);
p->upvalues = luaM_newarray(L, sizeupvalues, TString*, p->memcat);
p->sizeupvalues = sizeupvalues;
for (int j = 0; j < p->sizeupvalues; ++j)
{
p->upvalues[j] = readString(strings, data, size, offset);
}
}
protos[i] = p;
}
// "main" proto is pushed to Lua stack
uint32_t mainid = readVarInt(data, size, offset);
Proto* main = protos[mainid];
luaC_threadbarrier(L);
Closure* cl = luaF_newLclosure(L, 0, envt, main);
setclvalue(L, L->top, cl);
incr_top(L);
return 0;
}
int luau_load(lua_State* L, const char* chunkname, const char* data, size_t size, int env) int luau_load(lua_State* L, const char* chunkname, const char* data, size_t size, int env)
{ {
if (!FFlag::LuauLoadNoOomThrow)
return luau_load_DEPRECATED(L, chunkname, data, size, env);
// we will allocate a fair amount of memory so check GC before we do // we will allocate a fair amount of memory so check GC before we do
luaC_checkGC(L); luaC_checkGC(L);

View file

@ -58,6 +58,8 @@ LUAU_FASTFLAG(DebugLuauFreezeArena)
LUAU_FASTFLAG(DebugLuauAbortingChecks) LUAU_FASTFLAG(DebugLuauAbortingChecks)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
const double kTypecheckTimeoutSec = 4.0;
std::chrono::milliseconds kInterruptTimeout(10); std::chrono::milliseconds kInterruptTimeout(10);
std::chrono::time_point<std::chrono::system_clock> interruptDeadline; std::chrono::time_point<std::chrono::system_clock> interruptDeadline;
@ -175,6 +177,19 @@ static void setupFrontend(Luau::Frontend& frontend)
}; };
} }
static Luau::FrontendOptions getFrontendOptions()
{
Luau::FrontendOptions options;
options.retainFullTypeGraphs = true;
options.forAutocomplete = false;
options.runLintChecks = kFuzzLinter;
options.moduleTimeLimitSec = kTypecheckTimeoutSec;
return options;
}
struct FuzzFileResolver : Luau::FileResolver struct FuzzFileResolver : Luau::FileResolver
{ {
std::optional<Luau::SourceCode> readSource(const Luau::ModuleName& name) override std::optional<Luau::SourceCode> readSource(const Luau::ModuleName& name) override
@ -286,7 +301,7 @@ DEFINE_PROTO_FUZZER(const luau::ModuleSet& message)
{ {
static FuzzFileResolver fileResolver; static FuzzFileResolver fileResolver;
static FuzzConfigResolver configResolver; static FuzzConfigResolver configResolver;
static Luau::FrontendOptions defaultOptions{/*retainFullTypeGraphs*/ true, /*forAutocomplete*/ false, /*runLintChecks*/ kFuzzLinter}; static Luau::FrontendOptions defaultOptions = getFrontendOptions();
static Luau::Frontend frontend(&fileResolver, &configResolver, defaultOptions); static Luau::Frontend frontend(&fileResolver, &configResolver, defaultOptions);
static int once = (setupFrontend(frontend), 0); static int once = (setupFrontend(frontend), 0);

View file

@ -11,8 +11,6 @@
using namespace Luau; using namespace Luau;
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
struct JsonEncoderFixture struct JsonEncoderFixture
{ {
Allocator allocator; Allocator allocator;
@ -419,40 +417,20 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatDeclareFunction")
{ {
AstStat* statement = expectParseStatement("declare function foo(x: number): string"); AstStat* statement = expectParseStatement("declare function foo(x: number): string");
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
std::string_view expected = std::string_view expected =
R"({"type":"AstStatDeclareFunction","location":"0,0 - 0,39","attributes":[],"name":"foo","nameLocation":"0,17 - 0,20","params":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,24 - 0,30","name":"number","nameLocation":"0,24 - 0,30","parameters":[]}]},"paramNames":[{"type":"AstArgumentName","name":"x","location":"0,21 - 0,22"}],"vararg":false,"varargLocation":"0,0 - 0,0","retTypes":{"type":"AstTypePackExplicit","location":"0,33 - 0,39","typeList":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,33 - 0,39","name":"string","nameLocation":"0,33 - 0,39","parameters":[]}]}},"generics":[],"genericPacks":[]})"; R"({"type":"AstStatDeclareFunction","location":"0,0 - 0,39","attributes":[],"name":"foo","nameLocation":"0,17 - 0,20","params":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,24 - 0,30","name":"number","nameLocation":"0,24 - 0,30","parameters":[]}]},"paramNames":[{"type":"AstArgumentName","name":"x","location":"0,21 - 0,22"}],"vararg":false,"varargLocation":"0,0 - 0,0","retTypes":{"type":"AstTypePackExplicit","location":"0,33 - 0,39","typeList":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,33 - 0,39","name":"string","nameLocation":"0,33 - 0,39","parameters":[]}]}},"generics":[],"genericPacks":[]})";
CHECK(toJson(statement) == expected); CHECK(toJson(statement) == expected);
} }
else
{
std::string_view expected =
R"({"type":"AstStatDeclareFunction","location":"0,0 - 0,39","attributes":[],"name":"foo","nameLocation":"0,17 - 0,20","params":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,24 - 0,30","name":"number","nameLocation":"0,24 - 0,30","parameters":[]}]},"paramNames":[{"type":"AstArgumentName","name":"x","location":"0,21 - 0,22"}],"vararg":false,"varargLocation":"0,0 - 0,0","retTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,33 - 0,39","name":"string","nameLocation":"0,33 - 0,39","parameters":[]}]},"generics":[],"genericPacks":[]})";
CHECK(toJson(statement) == expected);
}
}
TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatDeclareFunction2") TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatDeclareFunction2")
{ {
AstStat* statement = expectParseStatement("declare function foo(x: number, ...: string): string"); AstStat* statement = expectParseStatement("declare function foo(x: number, ...: string): string");
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
std::string_view expected = std::string_view expected =
R"({"type":"AstStatDeclareFunction","location":"0,0 - 0,52","attributes":[],"name":"foo","nameLocation":"0,17 - 0,20","params":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,24 - 0,30","name":"number","nameLocation":"0,24 - 0,30","parameters":[]}],"tailType":{"type":"AstTypePackVariadic","location":"0,37 - 0,43","variadicType":{"type":"AstTypeReference","location":"0,37 - 0,43","name":"string","nameLocation":"0,37 - 0,43","parameters":[]}}},"paramNames":[{"type":"AstArgumentName","name":"x","location":"0,21 - 0,22"}],"vararg":true,"varargLocation":"0,32 - 0,35","retTypes":{"type":"AstTypePackExplicit","location":"0,46 - 0,52","typeList":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,46 - 0,52","name":"string","nameLocation":"0,46 - 0,52","parameters":[]}]}},"generics":[],"genericPacks":[]})"; R"({"type":"AstStatDeclareFunction","location":"0,0 - 0,52","attributes":[],"name":"foo","nameLocation":"0,17 - 0,20","params":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,24 - 0,30","name":"number","nameLocation":"0,24 - 0,30","parameters":[]}],"tailType":{"type":"AstTypePackVariadic","location":"0,37 - 0,43","variadicType":{"type":"AstTypeReference","location":"0,37 - 0,43","name":"string","nameLocation":"0,37 - 0,43","parameters":[]}}},"paramNames":[{"type":"AstArgumentName","name":"x","location":"0,21 - 0,22"}],"vararg":true,"varargLocation":"0,32 - 0,35","retTypes":{"type":"AstTypePackExplicit","location":"0,46 - 0,52","typeList":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,46 - 0,52","name":"string","nameLocation":"0,46 - 0,52","parameters":[]}]}},"generics":[],"genericPacks":[]})";
CHECK(toJson(statement) == expected); CHECK(toJson(statement) == expected);
} }
else
{
std::string_view expected =
R"({"type":"AstStatDeclareFunction","location":"0,0 - 0,52","attributes":[],"name":"foo","nameLocation":"0,17 - 0,20","params":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,24 - 0,30","name":"number","nameLocation":"0,24 - 0,30","parameters":[]}],"tailType":{"type":"AstTypePackVariadic","location":"0,37 - 0,43","variadicType":{"type":"AstTypeReference","location":"0,37 - 0,43","name":"string","nameLocation":"0,37 - 0,43","parameters":[]}}},"paramNames":[{"type":"AstArgumentName","name":"x","location":"0,21 - 0,22"}],"vararg":true,"varargLocation":"0,32 - 0,35","retTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,46 - 0,52","name":"string","nameLocation":"0,46 - 0,52","parameters":[]}]},"generics":[],"genericPacks":[]})";
CHECK(toJson(statement) == expected);
}
}
TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstAttr") TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstAttr")
{ {
@ -479,18 +457,9 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatDeclareClass")
REQUIRE(2 == root->body.size); REQUIRE(2 == root->body.size);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
std::string_view expected1 = std::string_view expected1 =
R"({"type":"AstStatDeclareClass","location":"1,22 - 4,11","name":"Foo","props":[{"name":"prop","nameLocation":"2,12 - 2,16","type":"AstDeclaredClassProp","luauType":{"type":"AstTypeReference","location":"2,18 - 2,24","name":"number","nameLocation":"2,18 - 2,24","parameters":[]},"location":"2,12 - 2,24"},{"name":"method","nameLocation":"3,21 - 3,27","type":"AstDeclaredClassProp","luauType":{"type":"AstTypeFunction","location":"3,12 - 3,54","attributes":[],"generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"3,39 - 3,45","name":"number","nameLocation":"3,39 - 3,45","parameters":[]}]},"argNames":[{"type":"AstArgumentName","name":"foo","location":"3,34 - 3,37"}],"returnTypes":{"type":"AstTypePackExplicit","location":"3,48 - 3,54","typeList":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"3,48 - 3,54","name":"string","nameLocation":"3,48 - 3,54","parameters":[]}]}}},"location":"3,12 - 3,54"}],"indexer":null})"; R"({"type":"AstStatDeclareClass","location":"1,22 - 4,11","name":"Foo","props":[{"name":"prop","nameLocation":"2,12 - 2,16","type":"AstDeclaredClassProp","luauType":{"type":"AstTypeReference","location":"2,18 - 2,24","name":"number","nameLocation":"2,18 - 2,24","parameters":[]},"location":"2,12 - 2,24"},{"name":"method","nameLocation":"3,21 - 3,27","type":"AstDeclaredClassProp","luauType":{"type":"AstTypeFunction","location":"3,12 - 3,54","attributes":[],"generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"3,39 - 3,45","name":"number","nameLocation":"3,39 - 3,45","parameters":[]}]},"argNames":[{"type":"AstArgumentName","name":"foo","location":"3,34 - 3,37"}],"returnTypes":{"type":"AstTypePackExplicit","location":"3,48 - 3,54","typeList":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"3,48 - 3,54","name":"string","nameLocation":"3,48 - 3,54","parameters":[]}]}}},"location":"3,12 - 3,54"}],"indexer":null})";
CHECK(toJson(root->body.data[0]) == expected1); CHECK(toJson(root->body.data[0]) == expected1);
}
else
{
std::string_view expected1 =
R"({"type":"AstStatDeclareClass","location":"1,22 - 4,11","name":"Foo","props":[{"name":"prop","nameLocation":"2,12 - 2,16","type":"AstDeclaredClassProp","luauType":{"type":"AstTypeReference","location":"2,18 - 2,24","name":"number","nameLocation":"2,18 - 2,24","parameters":[]},"location":"2,12 - 2,24"},{"name":"method","nameLocation":"3,21 - 3,27","type":"AstDeclaredClassProp","luauType":{"type":"AstTypeFunction","location":"3,12 - 3,54","attributes":[],"generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"3,39 - 3,45","name":"number","nameLocation":"3,39 - 3,45","parameters":[]}]},"argNames":[{"type":"AstArgumentName","name":"foo","location":"3,34 - 3,37"}],"returnTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"3,48 - 3,54","name":"string","nameLocation":"3,48 - 3,54","parameters":[]}]}},"location":"3,12 - 3,54"}],"indexer":null})";
CHECK(toJson(root->body.data[0]) == expected1);
}
std::string_view expected2 = std::string_view expected2 =
R"({"type":"AstStatDeclareClass","location":"6,22 - 8,11","name":"Bar","superName":"Foo","props":[{"name":"prop2","nameLocation":"7,12 - 7,17","type":"AstDeclaredClassProp","luauType":{"type":"AstTypeReference","location":"7,19 - 7,25","name":"string","nameLocation":"7,19 - 7,25","parameters":[]},"location":"7,12 - 7,25"}],"indexer":null})"; R"({"type":"AstStatDeclareClass","location":"6,22 - 8,11","name":"Bar","superName":"Foo","props":[{"name":"prop2","nameLocation":"7,12 - 7,17","type":"AstDeclaredClassProp","luauType":{"type":"AstTypeReference","location":"7,19 - 7,25","name":"string","nameLocation":"7,19 - 7,25","parameters":[]},"location":"7,12 - 7,25"}],"indexer":null})";
@ -501,19 +470,10 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_annotation")
{ {
AstStat* statement = expectParseStatement("type T = ((number) -> (string | nil)) & ((string) -> ())"); AstStat* statement = expectParseStatement("type T = ((number) -> (string | nil)) & ((string) -> ())");
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
std::string_view expected = std::string_view expected =
R"({"type":"AstStatTypeAlias","location":"0,0 - 0,56","name":"T","generics":[],"genericPacks":[],"value":{"type":"AstTypeIntersection","location":"0,9 - 0,56","types":[{"type":"AstTypeGroup","location":"0,9 - 0,37","inner":{"type":"AstTypeFunction","location":"0,10 - 0,36","attributes":[],"generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,11 - 0,17","name":"number","nameLocation":"0,11 - 0,17","parameters":[]}]},"argNames":[],"returnTypes":{"type":"AstTypePackExplicit","location":"0,22 - 0,36","typeList":{"type":"AstTypeList","types":[{"type":"AstTypeGroup","location":"0,22 - 0,36","inner":{"type":"AstTypeUnion","location":"0,23 - 0,35","types":[{"type":"AstTypeReference","location":"0,23 - 0,29","name":"string","nameLocation":"0,23 - 0,29","parameters":[]},{"type":"AstTypeReference","location":"0,32 - 0,35","name":"nil","nameLocation":"0,32 - 0,35","parameters":[]}]}}]}}}},{"type":"AstTypeGroup","location":"0,40 - 0,56","inner":{"type":"AstTypeFunction","location":"0,41 - 0,55","attributes":[],"generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,42 - 0,48","name":"string","nameLocation":"0,42 - 0,48","parameters":[]}]},"argNames":[],"returnTypes":{"type":"AstTypePackExplicit","location":"0,53 - 0,55","typeList":{"type":"AstTypeList","types":[]}}}}]},"exported":false})"; R"({"type":"AstStatTypeAlias","location":"0,0 - 0,56","name":"T","generics":[],"genericPacks":[],"value":{"type":"AstTypeIntersection","location":"0,9 - 0,56","types":[{"type":"AstTypeGroup","location":"0,9 - 0,37","inner":{"type":"AstTypeFunction","location":"0,10 - 0,36","attributes":[],"generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,11 - 0,17","name":"number","nameLocation":"0,11 - 0,17","parameters":[]}]},"argNames":[],"returnTypes":{"type":"AstTypePackExplicit","location":"0,22 - 0,36","typeList":{"type":"AstTypeList","types":[{"type":"AstTypeGroup","location":"0,22 - 0,36","inner":{"type":"AstTypeUnion","location":"0,23 - 0,35","types":[{"type":"AstTypeReference","location":"0,23 - 0,29","name":"string","nameLocation":"0,23 - 0,29","parameters":[]},{"type":"AstTypeReference","location":"0,32 - 0,35","name":"nil","nameLocation":"0,32 - 0,35","parameters":[]}]}}]}}}},{"type":"AstTypeGroup","location":"0,40 - 0,56","inner":{"type":"AstTypeFunction","location":"0,41 - 0,55","attributes":[],"generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,42 - 0,48","name":"string","nameLocation":"0,42 - 0,48","parameters":[]}]},"argNames":[],"returnTypes":{"type":"AstTypePackExplicit","location":"0,53 - 0,55","typeList":{"type":"AstTypeList","types":[]}}}}]},"exported":false})";
CHECK(toJson(statement) == expected); CHECK(toJson(statement) == expected);
} }
else
{
std::string_view expected =
R"({"type":"AstStatTypeAlias","location":"0,0 - 0,56","name":"T","generics":[],"genericPacks":[],"value":{"type":"AstTypeIntersection","location":"0,9 - 0,56","types":[{"type":"AstTypeGroup","location":"0,9 - 0,37","inner":{"type":"AstTypeFunction","location":"0,10 - 0,36","attributes":[],"generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,11 - 0,17","name":"number","nameLocation":"0,11 - 0,17","parameters":[]}]},"argNames":[],"returnTypes":{"type":"AstTypeList","types":[{"type":"AstTypeGroup","location":"0,22 - 0,36","inner":{"type":"AstTypeUnion","location":"0,23 - 0,35","types":[{"type":"AstTypeReference","location":"0,23 - 0,29","name":"string","nameLocation":"0,23 - 0,29","parameters":[]},{"type":"AstTypeReference","location":"0,32 - 0,35","name":"nil","nameLocation":"0,32 - 0,35","parameters":[]}]}}]}}},{"type":"AstTypeGroup","location":"0,40 - 0,56","inner":{"type":"AstTypeFunction","location":"0,41 - 0,55","attributes":[],"generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,42 - 0,48","name":"string","nameLocation":"0,42 - 0,48","parameters":[]}]},"argNames":[],"returnTypes":{"type":"AstTypeList","types":[]}}}]},"exported":false})";
CHECK(toJson(statement) == expected);
}
}
TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_type_literal") TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_type_literal")
{ {
@ -541,19 +501,10 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstTypeFunction")
{ {
AstStat* statement = expectParseStatement(R"(type fun = (string, bool, named: number) -> ())"); AstStat* statement = expectParseStatement(R"(type fun = (string, bool, named: number) -> ())");
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
std::string_view expected = std::string_view expected =
R"({"type":"AstStatTypeAlias","location":"0,0 - 0,46","name":"fun","generics":[],"genericPacks":[],"value":{"type":"AstTypeFunction","location":"0,11 - 0,46","attributes":[],"generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,12 - 0,18","name":"string","nameLocation":"0,12 - 0,18","parameters":[]},{"type":"AstTypeReference","location":"0,20 - 0,24","name":"bool","nameLocation":"0,20 - 0,24","parameters":[]},{"type":"AstTypeReference","location":"0,33 - 0,39","name":"number","nameLocation":"0,33 - 0,39","parameters":[]}]},"argNames":[null,null,{"type":"AstArgumentName","name":"named","location":"0,26 - 0,31"}],"returnTypes":{"type":"AstTypePackExplicit","location":"0,44 - 0,46","typeList":{"type":"AstTypeList","types":[]}}},"exported":false})"; R"({"type":"AstStatTypeAlias","location":"0,0 - 0,46","name":"fun","generics":[],"genericPacks":[],"value":{"type":"AstTypeFunction","location":"0,11 - 0,46","attributes":[],"generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,12 - 0,18","name":"string","nameLocation":"0,12 - 0,18","parameters":[]},{"type":"AstTypeReference","location":"0,20 - 0,24","name":"bool","nameLocation":"0,20 - 0,24","parameters":[]},{"type":"AstTypeReference","location":"0,33 - 0,39","name":"number","nameLocation":"0,33 - 0,39","parameters":[]}]},"argNames":[null,null,{"type":"AstArgumentName","name":"named","location":"0,26 - 0,31"}],"returnTypes":{"type":"AstTypePackExplicit","location":"0,44 - 0,46","typeList":{"type":"AstTypeList","types":[]}}},"exported":false})";
CHECK(toJson(statement) == expected); CHECK(toJson(statement) == expected);
} }
else
{
std::string_view expected =
R"({"type":"AstStatTypeAlias","location":"0,0 - 0,46","name":"fun","generics":[],"genericPacks":[],"value":{"type":"AstTypeFunction","location":"0,11 - 0,46","attributes":[],"generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,12 - 0,18","name":"string","nameLocation":"0,12 - 0,18","parameters":[]},{"type":"AstTypeReference","location":"0,20 - 0,24","name":"bool","nameLocation":"0,20 - 0,24","parameters":[]},{"type":"AstTypeReference","location":"0,33 - 0,39","name":"number","nameLocation":"0,33 - 0,39","parameters":[]}]},"argNames":[null,null,{"type":"AstArgumentName","name":"named","location":"0,26 - 0,31"}],"returnTypes":{"type":"AstTypeList","types":[]}},"exported":false})";
CHECK(toJson(statement) == expected);
}
}
TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstTypeError") TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstTypeError")
{ {

View file

@ -21,7 +21,7 @@ LUAU_FASTFLAG(LuauSetMetatableDoesNotTimeTravel)
LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauEagerGeneralization4)
LUAU_FASTFLAG(LuauExpectedTypeVisitor) LUAU_FASTFLAG(LuauExpectedTypeVisitor)
LUAU_FASTFLAG(LuauImplicitTableIndexerKeys) LUAU_FASTFLAG(LuauImplicitTableIndexerKeys2)
using namespace Luau; using namespace Luau;
@ -4621,7 +4621,7 @@ TEST_CASE_FIXTURE(ACFixture, "autocomplete_implicit_named_index_index_expr")
// Somewhat surprisingly, the old solver didn't cover this case. // Somewhat surprisingly, the old solver didn't cover this case.
{FFlag::LuauSolverV2, true}, {FFlag::LuauSolverV2, true},
{FFlag::LuauExpectedTypeVisitor, true}, {FFlag::LuauExpectedTypeVisitor, true},
{FFlag::LuauImplicitTableIndexerKeys, true}, {FFlag::LuauImplicitTableIndexerKeys2, true},
}; };
check(R"( check(R"(
@ -4636,8 +4636,62 @@ TEST_CASE_FIXTURE(ACFixture, "autocomplete_implicit_named_index_index_expr")
auto ac = autocomplete('1'); auto ac = autocomplete('1');
CHECK_EQ(ac.entryMap.count("A"), 1); CHECK_EQ(ac.entryMap.count("A"), 1);
CHECK_EQ(ac.entryMap["A"].kind, AutocompleteEntryKind::String);
CHECK_EQ(ac.entryMap.count("B"), 1); CHECK_EQ(ac.entryMap.count("B"), 1);
CHECK_EQ(ac.entryMap["B"].kind, AutocompleteEntryKind::String);
CHECK_EQ(ac.entryMap.count("C"), 1); CHECK_EQ(ac.entryMap.count("C"), 1);
CHECK_EQ(ac.entryMap["C"].kind, AutocompleteEntryKind::String);
}
TEST_CASE_FIXTURE(ACFixture, "autocomplete_implicit_named_index_index_expr_without_annotation")
{
ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true},
{FFlag::LuauExpectedTypeVisitor, true},
{FFlag::LuauImplicitTableIndexerKeys2, false},
};
check(R"(
local foo = {
["Item/Foo"] = 42,
["Item/Bar"] = "it's true",
["Item/Baz"] = true,
}
foo["@1"]
)");
auto ac = autocomplete('1');
auto checkEntry = [&](auto key, auto type)
{
REQUIRE_EQ(ac.entryMap.count(key), 1);
auto entry = ac.entryMap.at(key);
CHECK_EQ(entry.kind, AutocompleteEntryKind::Property);
REQUIRE(entry.type);
CHECK_EQ(type, toString(*entry.type));
};
checkEntry("Item/Foo", "number");
checkEntry("Item/Bar", "string");
checkEntry("Item/Baz", "boolean");
}
TEST_CASE_FIXTURE(ACFixture, "bidirectional_autocomplete_in_function_call")
{
ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true},
{FFlag::LuauExpectedTypeVisitor, true},
};
check(R"(
local function take(_: { choice: "left" | "right" }) end
take({ choice = "@1" })
)");
auto ac = autocomplete('1');
CHECK_EQ(ac.entryMap.count("left"), 1);
CHECK_EQ(ac.entryMap.count("right"), 1);
} }
TEST_SUITE_END(); TEST_SUITE_END();

View file

@ -17,6 +17,7 @@ namespace Luau
std::string rep(const std::string& s, size_t n); std::string rep(const std::string& s, size_t n);
} }
LUAU_FASTFLAG(LuauCompileInlineNonConstInit)
LUAU_FASTINT(LuauCompileInlineDepth) LUAU_FASTINT(LuauCompileInlineDepth)
LUAU_FASTINT(LuauCompileInlineThreshold) LUAU_FASTINT(LuauCompileInlineThreshold)
LUAU_FASTINT(LuauCompileInlineThresholdMaxBoost) LUAU_FASTINT(LuauCompileInlineThresholdMaxBoost)
@ -24,6 +25,7 @@ LUAU_FASTINT(LuauCompileLoopUnrollThreshold)
LUAU_FASTINT(LuauCompileLoopUnrollThresholdMaxBoost) LUAU_FASTINT(LuauCompileLoopUnrollThresholdMaxBoost)
LUAU_FASTINT(LuauRecursionLimit) LUAU_FASTINT(LuauRecursionLimit)
LUAU_FASTFLAG(LuauCompileFixTypeFunctionSkip) LUAU_FASTFLAG(LuauCompileFixTypeFunctionSkip)
LUAU_FASTFLAG(LuauCompileCostModelConstants)
using namespace Luau; using namespace Luau;
@ -121,6 +123,20 @@ static std::string compileTypeTable(const char* source)
return bcb.dumpTypeInfo(); return bcb.dumpTypeInfo();
} }
static std::string compileWithRemarks(const char* source)
{
Luau::BytecodeBuilder bcb;
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Source | Luau::BytecodeBuilder::Dump_Remarks);
bcb.setDumpSource(source);
Luau::CompileOptions options;
options.optimizationLevel = 2;
Luau::compileOrThrow(bcb, source, options);
return bcb.dumpSourceRemarks();
}
TEST_SUITE_BEGIN("Compiler"); TEST_SUITE_BEGIN("Compiler");
TEST_CASE("BytecodeIsStable") TEST_CASE("BytecodeIsStable")
@ -3598,9 +3614,12 @@ RETURN R4 1
)"); )");
} }
TEST_CASE("SourceRemarks") TEST_CASE("CostModelRemarks")
{ {
const char* source = R"( ScopedFastFlag luauCompileCostModelConstants{FFlag::LuauCompileCostModelConstants, true};
CHECK_EQ(
compileWithRemarks(R"(
local a, b = ... local a, b = ...
local function foo(x) local function foo(x)
@ -3608,20 +3627,8 @@ local function foo(x)
end end
return foo(a) + foo(assert(b)) return foo(a) + foo(assert(b))
)"; )"),
R"(
Luau::BytecodeBuilder bcb;
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Source | Luau::BytecodeBuilder::Dump_Remarks);
bcb.setDumpSource(source);
Luau::CompileOptions options;
options.optimizationLevel = 2;
Luau::compileOrThrow(bcb, source, options);
std::string remarks = bcb.dumpSourceRemarks();
CHECK_EQ(remarks, R"(
local a, b = ... local a, b = ...
local function foo(x) local function foo(x)
@ -3632,7 +3639,149 @@ end
-- remark: builtin assert/1 -- remark: builtin assert/1
-- remark: inlining succeeded (cost 2, profit 2.50x, depth 0) -- remark: inlining succeeded (cost 2, profit 2.50x, depth 0)
return foo(a) + foo(assert(b)) return foo(a) + foo(assert(b))
)"); )"
);
CHECK_EQ(
compileWithRemarks(R"(
local value = true
local function foo()
return value
end
return foo()
)"),
R"(
local value = true
local function foo()
return value
end
-- remark: inlining succeeded (cost 0, profit 3.00x, depth 0)
return foo()
)"
);
CHECK_EQ(
compileWithRemarks(R"(
local value = true
local function foo()
return not value
end
return foo()
)"),
R"(
local value = true
local function foo()
return not value
end
-- remark: inlining succeeded (cost 0, profit 3.00x, depth 0)
return foo()
)"
);
CHECK_EQ(
compileWithRemarks(R"(
local function foo()
local s = 0
for i = 1, 100 do s += i end
return s
end
return foo()
)"),
R"(
local function foo()
local s = 0
-- remark: loop unroll failed: too many iterations (100)
for i = 1, 100 do s += i end
return s
end
-- remark: inlining failed: too expensive (cost 127, profit 1.02x)
return foo()
)"
);
CHECK_EQ(
compileWithRemarks(R"(
local function foo()
local s = 0
for i = 1, 4 * 25 do s += i end
return s
end
return foo()
)"),
R"(
local function foo()
local s = 0
-- remark: loop unroll failed: too many iterations (100)
for i = 1, 4 * 25 do s += i end
return s
end
-- remark: inlining failed: too expensive (cost 127, profit 1.02x)
return foo()
)"
);
CHECK_EQ(
compileWithRemarks(R"(
local x = ...
local function test(a)
while a < 0 do
a += 1
end
for i=10,1,-1 do
a += 1
end
for i in pairs({}) do
a += 1
if a % 2 == 0 then continue end
end
repeat
a += 1
if a % 2 == 0 then break end
until a > 10
return a
end
local a = test(x)
local b = test(2)
)"),
R"(
local x = ...
local function test(a)
while a < 0 do
a += 1
end
-- remark: loop unroll succeeded (iterations 10, cost 10, profit 2.00x)
for i=10,1,-1 do
a += 1
end
-- remark: allocation: table hash 0
for i in pairs({}) do
a += 1
if a % 2 == 0 then continue end
end
repeat
a += 1
if a % 2 == 0 then break end
until a > 10
return a
end
-- remark: inlining failed: too expensive (cost 76, profit 1.03x)
local a = test(x)
-- remark: inlining failed: too expensive (cost 73, profit 1.08x)
local b = test(2)
)"
);
} }
TEST_CASE("AssignmentConflict") TEST_CASE("AssignmentConflict")
@ -7473,6 +7622,74 @@ RETURN R1 1
); );
} }
TEST_CASE("InlineNonConstInitializers")
{
ScopedFastFlag luauCompileInlineNonConstInit{FFlag::LuauCompileInlineNonConstInit, true};
CHECK_EQ(
"\n" + compileFunction(
R"(
local function caller(f)
f(1)
end
local function callback(n)
print(n + 5)
end
caller(callback)
)",
2,
2
),
R"(
DUPCLOSURE R0 K0 ['caller']
DUPCLOSURE R1 K1 ['callback']
GETIMPORT R2 3 [print]
LOADN R3 6
CALL R2 1 0
RETURN R0 0
)"
);
}
TEST_CASE("InlineNonConstInitializers2")
{
ScopedFastFlag luauCompileInlineNonConstInit{FFlag::LuauCompileInlineNonConstInit, true};
CHECK_EQ(
"\n" + compileFunction(
R"(
local x, y, z = ...
local function test(a, b, c, comp)
return comp(a, b) and comp(b, c)
end
local function greater(a, b)
return a > b
end
test(x, y, z, greater)
)",
2,
2
),
R"(
GETVARARGS R0 3
DUPCLOSURE R3 K0 ['test']
DUPCLOSURE R4 K1 ['greater']
JUMPIFLT R1 R0 L0
LOADB R5 0 +1
L0: LOADB R5 1
L1: JUMPIFNOT R5 L3
JUMPIFLT R2 R1 L2
LOADB R5 0 +1
L2: LOADB R5 1
L3: RETURN R0 0
)"
);
}
TEST_CASE("ReturnConsecutive") TEST_CASE("ReturnConsecutive")
{ {
// we can return a single local directly // we can return a single local directly

View file

@ -37,9 +37,8 @@ void luau_callhook(lua_State* L, lua_Hook hook, void* userdata);
LUAU_FASTFLAG(DebugLuauAbortingChecks) LUAU_FASTFLAG(DebugLuauAbortingChecks)
LUAU_FASTINT(CodegenHeuristicsInstructionLimit) LUAU_FASTINT(CodegenHeuristicsInstructionLimit)
LUAU_FASTFLAG(LuauYieldableContinuations) LUAU_FASTFLAG(LuauYieldableContinuations)
LUAU_FASTFLAG(LuauCurrentLineBounds)
LUAU_FASTFLAG(LuauLoadNoOomThrow)
LUAU_FASTFLAG(LuauHeapNameDetails) LUAU_FASTFLAG(LuauHeapNameDetails)
LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps)
LUAU_DYNAMIC_FASTFLAG(LuauGcAgainstOom) LUAU_DYNAMIC_FASTFLAG(LuauGcAgainstOom)
static lua_CompileOptions defaultOptions() static lua_CompileOptions defaultOptions()
@ -1211,7 +1210,16 @@ static void populateRTTI(lua_State* L, Luau::TypeId type)
for (const auto& [name, prop] : t->props) for (const auto& [name, prop] : t->props)
{ {
populateRTTI(L, prop.type()); if (FFlag::LuauRemoveTypeCallsForReadWriteProps)
{
if (prop.readTy)
populateRTTI(L, *prop.readTy);
else if (prop.writeTy)
populateRTTI(L, *prop.writeTy);
}
else
populateRTTI(L, prop.type_DEPRECATED());
lua_setfield(L, -2, name.c_str()); lua_setfield(L, -2, name.c_str());
} }
} }
@ -1469,8 +1477,6 @@ TEST_CASE("Debugger")
TEST_CASE("InterruptInspection") TEST_CASE("InterruptInspection")
{ {
ScopedFastFlag luauCurrentLineBounds{FFlag::LuauCurrentLineBounds, true};
static bool skipbreak = false; static bool skipbreak = false;
runConformance( runConformance(
@ -1517,8 +1523,6 @@ TEST_CASE("InterruptInspection")
TEST_CASE("InterruptErrorInspection") TEST_CASE("InterruptErrorInspection")
{ {
ScopedFastFlag luauCurrentLineBounds{FFlag::LuauCurrentLineBounds, true};
// for easy access in no-capture lambda // for easy access in no-capture lambda
static int target = 0; static int target = 0;
static int step = 0; static int step = 0;
@ -3237,8 +3241,6 @@ TEST_CASE("HugeFunction")
TEST_CASE("HugeFunctionLoadFailure") TEST_CASE("HugeFunctionLoadFailure")
{ {
ScopedFastFlag luauLoadNoOomThrow{FFlag::LuauLoadNoOomThrow, true};
// This test case verifies that if an out-of-memory error occurs inside of // This test case verifies that if an out-of-memory error occurs inside of
// luau_load, we are not left with any GC objects in inconsistent states // luau_load, we are not left with any GC objects in inconsistent states
// that would cause issues during garbage collection. // that would cause issues during garbage collection.

View file

@ -5,6 +5,8 @@
#include "doctest.h" #include "doctest.h"
LUAU_FASTFLAG(LuauCompileCostModelConstants)
using namespace Luau; using namespace Luau;
namespace Luau namespace Luau
@ -12,7 +14,7 @@ namespace Luau
namespace Compile namespace Compile
{ {
uint64_t modelCost(AstNode* root, AstLocal* const* vars, size_t varCount, const DenseHashMap<AstExprCall*, int>& builtins); uint64_t modelCost(AstNode* root, AstLocal* const* vars, size_t varCount);
int computeCost(uint64_t model, const bool* varsConst, size_t varCount); int computeCost(uint64_t model, const bool* varsConst, size_t varCount);
} // namespace Compile } // namespace Compile
@ -31,7 +33,7 @@ static uint64_t modelFunction(const char* source)
AstStatFunction* func = result.root->body.data[0]->as<AstStatFunction>(); AstStatFunction* func = result.root->body.data[0]->as<AstStatFunction>();
REQUIRE(func); REQUIRE(func);
return Luau::Compile::modelCost(func->func->body, func->func->args.data, func->func->args.size, DenseHashMap<AstExprCall*, int>{nullptr}); return Luau::Compile::modelCost(func->func->body, func->func->args.data, func->func->args.size);
} }
TEST_CASE("Expression") TEST_CASE("Expression")
@ -133,6 +135,8 @@ end
TEST_CASE("ControlFlow") TEST_CASE("ControlFlow")
{ {
ScopedFastFlag luauCompileCostModelConstants{FFlag::LuauCompileCostModelConstants, false};
uint64_t model = modelFunction(R"( uint64_t model = modelFunction(R"(
function test(a) function test(a)
while a < 0 do while a < 0 do

View file

@ -11,9 +11,11 @@
using namespace Luau; using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2); LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(DebugLuauFreezeArena); LUAU_FASTFLAG(DebugLuauFreezeArena)
LUAU_FASTINT(LuauTypeCloneIterationLimit); LUAU_FASTINT(LuauTypeCloneIterationLimit)
LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps)
TEST_SUITE_BEGIN("ModuleTests"); TEST_SUITE_BEGIN("ModuleTests");
TEST_CASE_FIXTURE(Fixture, "is_within_comment") TEST_CASE_FIXTURE(Fixture, "is_within_comment")
@ -136,7 +138,8 @@ TEST_CASE_FIXTURE(Fixture, "deepClone_cyclic_table")
CHECK_EQ(std::optional<std::string>{"Cyclic"}, ttv->syntheticName); CHECK_EQ(std::optional<std::string>{"Cyclic"}, ttv->syntheticName);
TypeId methodType = ttv->props["get"].type();
TypeId methodType = FFlag::LuauRemoveTypeCallsForReadWriteProps ? *ttv->props["get"].readTy : ttv->props["get"].type_DEPRECATED();
REQUIRE(methodType != nullptr); REQUIRE(methodType != nullptr);
const FunctionType* ftv = get<FunctionType>(methodType); const FunctionType* ftv = get<FunctionType>(methodType);
@ -169,7 +172,7 @@ TEST_CASE_FIXTURE(Fixture, "deepClone_cyclic_table_2")
TableType* ctt = getMutable<TableType>(cloneTy); TableType* ctt = getMutable<TableType>(cloneTy);
REQUIRE(ctt); REQUIRE(ctt);
TypeId clonedMethodType = ctt->props["get"].type(); TypeId clonedMethodType = FFlag::LuauRemoveTypeCallsForReadWriteProps ? *ctt->props["get"].readTy : ctt->props["get"].type_DEPRECATED();
REQUIRE(clonedMethodType); REQUIRE(clonedMethodType);
const FunctionType* cmf = get<FunctionType>(clonedMethodType); const FunctionType* cmf = get<FunctionType>(clonedMethodType);
@ -198,7 +201,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "builtin_types_point_into_globalTypes_arena")
TableType* exportsTable = getMutable<TableType>(*exports); TableType* exportsTable = getMutable<TableType>(*exports);
REQUIRE(exportsTable != nullptr); REQUIRE(exportsTable != nullptr);
TypeId signType = exportsTable->props["sign"].type(); TypeId signType = FFlag::LuauRemoveTypeCallsForReadWriteProps ? *exportsTable->props["sign"].readTy : exportsTable->props["sign"].type_DEPRECATED();
REQUIRE(signType != nullptr); REQUIRE(signType != nullptr);
CHECK(!isInArena(signType, module->interfaceTypes)); CHECK(!isInArena(signType, module->interfaceTypes));
@ -352,8 +355,17 @@ TEST_CASE_FIXTURE(Fixture, "clone_iteration_limit")
for (int i = 0; i < nesting; i++) for (int i = 0; i < nesting; i++)
{ {
TableType* ttv = getMutable<TableType>(nested); TableType* ttv = getMutable<TableType>(nested);
if (FFlag::LuauRemoveTypeCallsForReadWriteProps)
{
ttv->props["a"].readTy = src.addType(TableType{});
ttv->props["a"].writeTy = ttv->props["a"].readTy;
nested = *ttv->props["a"].readTy;
}
else
{
ttv->props["a"].setType(src.addType(TableType{})); ttv->props["a"].setType(src.addType(TableType{}));
nested = ttv->props["a"].type(); nested = ttv->props["a"].type_DEPRECATED();
}
} }
TypeArena dest; TypeArena dest;
@ -444,7 +456,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "do_not_clone_reexports")
TypeId typeB = modBiter->second.type; TypeId typeB = modBiter->second.type;
TableType* tableB = getMutable<TableType>(typeB); TableType* tableB = getMutable<TableType>(typeB);
REQUIRE(tableB); REQUIRE(tableB);
CHECK(typeA == tableB->props["q"].type()); if (FFlag::LuauRemoveTypeCallsForReadWriteProps)
CHECK(typeA == tableB->props["q"].readTy);
else
CHECK(typeA == tableB->props["q"].type_DEPRECATED());
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "do_not_clone_types_of_reexported_values") TEST_CASE_FIXTURE(BuiltinsFixture, "do_not_clone_types_of_reexported_values")
@ -478,7 +493,13 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "do_not_clone_types_of_reexported_values")
TableType* tableB = getMutable<TableType>(*typeB); TableType* tableB = getMutable<TableType>(*typeB);
REQUIRE_MESSAGE(tableB, "Expected a table, but got " << toString(*typeB)); REQUIRE_MESSAGE(tableB, "Expected a table, but got " << toString(*typeB));
CHECK(tableA->props["a"].type() == tableB->props["b"].type()); if (FFlag::LuauRemoveTypeCallsForReadWriteProps)
{
CHECK(tableA->props["a"].readTy == tableB->props["b"].readTy);
CHECK(tableA->props["a"].writeTy == tableB->props["b"].writeTy);
}
else
CHECK(tableA->props["a"].type_DEPRECATED() == tableB->props["b"].type_DEPRECATED());
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "clone_table_bound_to_table_bound_to_table") TEST_CASE_FIXTURE(BuiltinsFixture, "clone_table_bound_to_table_bound_to_table")

View file

@ -177,7 +177,7 @@ TEST_CASE_FIXTURE(Fixture, "table_props_are_any")
REQUIRE(ttv != nullptr); REQUIRE(ttv != nullptr);
REQUIRE(ttv->props.count("foo")); REQUIRE(ttv->props.count("foo"));
TypeId fooProp = ttv->props["foo"].type(); TypeId fooProp = ttv->props["foo"].type_DEPRECATED();
REQUIRE(fooProp != nullptr); REQUIRE(fooProp != nullptr);
CHECK_EQ(*fooProp, *getBuiltins()->anyType); CHECK_EQ(*fooProp, *getBuiltins()->anyType);
@ -200,9 +200,9 @@ TEST_CASE_FIXTURE(Fixture, "inline_table_props_are_also_any")
TableType* ttv = getMutable<TableType>(requireType("T")); TableType* ttv = getMutable<TableType>(requireType("T"));
REQUIRE_MESSAGE(ttv, "Should be a table: " << toString(requireType("T"))); REQUIRE_MESSAGE(ttv, "Should be a table: " << toString(requireType("T")));
CHECK_EQ(*getBuiltins()->anyType, *ttv->props["one"].type()); CHECK_EQ(*getBuiltins()->anyType, *ttv->props["one"].type_DEPRECATED());
CHECK_EQ(*getBuiltins()->anyType, *ttv->props["two"].type()); CHECK_EQ(*getBuiltins()->anyType, *ttv->props["two"].type_DEPRECATED());
CHECK_MESSAGE(get<FunctionType>(follow(ttv->props["three"].type())), "Should be a function: " << *ttv->props["three"].type()); CHECK_MESSAGE(get<FunctionType>(follow(ttv->props["three"].type_DEPRECATED())), "Should be a function: " << *ttv->props["three"].type_DEPRECATED());
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_iterator_variables_are_any") TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_iterator_variables_are_any")

View file

@ -17,10 +17,8 @@ LUAU_FASTINT(LuauRecursionLimit)
LUAU_FASTINT(LuauTypeLengthLimit) LUAU_FASTINT(LuauTypeLengthLimit)
LUAU_FASTINT(LuauParseErrorLimit) LUAU_FASTINT(LuauParseErrorLimit)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
LUAU_FASTFLAG(LuauParseStringIndexer) LUAU_FASTFLAG(LuauParseStringIndexer)
LUAU_FASTFLAG(LuauDeclareExternType) LUAU_FASTFLAG(LuauDeclareExternType)
LUAU_FASTFLAG(LuauStoreCSTData2)
LUAU_DYNAMIC_FASTFLAG(DebugLuauReportReturnTypeVariadicWithTypeSuffix) LUAU_DYNAMIC_FASTFLAG(DebugLuauReportReturnTypeVariadicWithTypeSuffix)
// Clip with DebugLuauReportReturnTypeVariadicWithTypeSuffix // Clip with DebugLuauReportReturnTypeVariadicWithTypeSuffix
@ -152,21 +150,12 @@ TEST_CASE_FIXTURE(Fixture, "functions_can_have_return_annotations")
AstStatFunction* statFunction = block->body.data[0]->as<AstStatFunction>(); AstStatFunction* statFunction = block->body.data[0]->as<AstStatFunction>();
REQUIRE(statFunction != nullptr); REQUIRE(statFunction != nullptr);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
REQUIRE(statFunction->func->returnAnnotation); REQUIRE(statFunction->func->returnAnnotation);
auto typePack = statFunction->func->returnAnnotation->as<AstTypePackExplicit>(); auto typePack = statFunction->func->returnAnnotation->as<AstTypePackExplicit>();
REQUIRE(typePack); REQUIRE(typePack);
CHECK_EQ(typePack->typeList.types.size, 1); CHECK_EQ(typePack->typeList.types.size, 1);
CHECK(typePack->typeList.tailType == nullptr); CHECK(typePack->typeList.tailType == nullptr);
} }
else
{
REQUIRE(statFunction->func->returnAnnotation_DEPRECATED.has_value());
CHECK_EQ(statFunction->func->returnAnnotation_DEPRECATED->types.size, 1);
CHECK(statFunction->func->returnAnnotation_DEPRECATED->tailType == nullptr);
}
}
TEST_CASE_FIXTURE(Fixture, "functions_can_have_a_function_type_annotation") TEST_CASE_FIXTURE(Fixture, "functions_can_have_a_function_type_annotation")
{ {
@ -180,8 +169,6 @@ TEST_CASE_FIXTURE(Fixture, "functions_can_have_a_function_type_annotation")
AstStatFunction* statFunc = block->body.data[0]->as<AstStatFunction>(); AstStatFunction* statFunc = block->body.data[0]->as<AstStatFunction>();
REQUIRE(statFunc != nullptr); REQUIRE(statFunc != nullptr);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
REQUIRE(statFunc->func->returnAnnotation); REQUIRE(statFunc->func->returnAnnotation);
auto typePack = statFunc->func->returnAnnotation->as<AstTypePackExplicit>(); auto typePack = statFunc->func->returnAnnotation->as<AstTypePackExplicit>();
REQUIRE(typePack); REQUIRE(typePack);
@ -192,17 +179,6 @@ TEST_CASE_FIXTURE(Fixture, "functions_can_have_a_function_type_annotation")
AstTypeFunction* funTy = retTypes.data[0]->as<AstTypeFunction>(); AstTypeFunction* funTy = retTypes.data[0]->as<AstTypeFunction>();
REQUIRE(funTy != nullptr); REQUIRE(funTy != nullptr);
} }
else
{
REQUIRE(statFunc->func->returnAnnotation_DEPRECATED.has_value());
CHECK(statFunc->func->returnAnnotation_DEPRECATED->tailType == nullptr);
AstArray<AstType*>& retTypes = statFunc->func->returnAnnotation_DEPRECATED->types;
REQUIRE(retTypes.size == 1);
AstTypeFunction* funTy = retTypes.data[0]->as<AstTypeFunction>();
REQUIRE(funTy != nullptr);
}
}
TEST_CASE_FIXTURE(Fixture, "function_return_type_should_disambiguate_from_function_type_and_multiple_returns") TEST_CASE_FIXTURE(Fixture, "function_return_type_should_disambiguate_from_function_type_and_multiple_returns")
{ {
@ -216,8 +192,6 @@ TEST_CASE_FIXTURE(Fixture, "function_return_type_should_disambiguate_from_functi
AstStatFunction* statFunc = block->body.data[0]->as<AstStatFunction>(); AstStatFunction* statFunc = block->body.data[0]->as<AstStatFunction>();
REQUIRE(statFunc != nullptr); REQUIRE(statFunc != nullptr);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
REQUIRE(statFunc->func->returnAnnotation); REQUIRE(statFunc->func->returnAnnotation);
auto typePack = statFunc->func->returnAnnotation->as<AstTypePackExplicit>(); auto typePack = statFunc->func->returnAnnotation->as<AstTypePackExplicit>();
REQUIRE(typePack); REQUIRE(typePack);
@ -233,22 +207,6 @@ TEST_CASE_FIXTURE(Fixture, "function_return_type_should_disambiguate_from_functi
REQUIRE(ty1 != nullptr); REQUIRE(ty1 != nullptr);
REQUIRE(ty1->name == "string"); REQUIRE(ty1->name == "string");
} }
else
{
REQUIRE(statFunc->func->returnAnnotation_DEPRECATED.has_value());
CHECK(statFunc->func->returnAnnotation_DEPRECATED->tailType == nullptr);
AstArray<AstType*>& retTypes = statFunc->func->returnAnnotation_DEPRECATED->types;
REQUIRE(retTypes.size == 2);
AstTypeReference* ty0 = retTypes.data[0]->as<AstTypeReference>();
REQUIRE(ty0 != nullptr);
REQUIRE(ty0->name == "number");
AstTypeReference* ty1 = retTypes.data[1]->as<AstTypeReference>();
REQUIRE(ty1 != nullptr);
REQUIRE(ty1->name == "string");
}
}
TEST_CASE_FIXTURE(Fixture, "function_return_type_should_parse_as_function_type_annotation_with_no_args") TEST_CASE_FIXTURE(Fixture, "function_return_type_should_parse_as_function_type_annotation_with_no_args")
{ {
@ -262,8 +220,6 @@ TEST_CASE_FIXTURE(Fixture, "function_return_type_should_parse_as_function_type_a
AstStatFunction* statFunc = block->body.data[0]->as<AstStatFunction>(); AstStatFunction* statFunc = block->body.data[0]->as<AstStatFunction>();
REQUIRE(statFunc != nullptr); REQUIRE(statFunc != nullptr);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
REQUIRE(statFunc->func->returnAnnotation); REQUIRE(statFunc->func->returnAnnotation);
auto typePack = statFunc->func->returnAnnotation->as<AstTypePackExplicit>(); auto typePack = statFunc->func->returnAnnotation->as<AstTypePackExplicit>();
REQUIRE(typePack); REQUIRE(typePack);
@ -284,24 +240,6 @@ TEST_CASE_FIXTURE(Fixture, "function_return_type_should_parse_as_function_type_a
REQUIRE(ty != nullptr); REQUIRE(ty != nullptr);
REQUIRE(ty->name == "nil"); REQUIRE(ty->name == "nil");
} }
else
{
REQUIRE(statFunc->func->returnAnnotation_DEPRECATED.has_value());
CHECK(statFunc->func->returnAnnotation_DEPRECATED->tailType == nullptr);
AstArray<AstType*>& retTypes = statFunc->func->returnAnnotation_DEPRECATED->types;
REQUIRE(retTypes.size == 1);
AstTypeFunction* funTy = retTypes.data[0]->as<AstTypeFunction>();
REQUIRE(funTy != nullptr);
REQUIRE(funTy->argTypes.types.size == 0);
CHECK(funTy->argTypes.tailType == nullptr);
CHECK(funTy->returnTypes_DEPRECATED.tailType == nullptr);
AstTypeReference* ty = funTy->returnTypes_DEPRECATED.types.data[0]->as<AstTypeReference>();
REQUIRE(ty != nullptr);
REQUIRE(ty->name == "nil");
}
}
TEST_CASE_FIXTURE(Fixture, "annotations_can_be_tables") TEST_CASE_FIXTURE(Fixture, "annotations_can_be_tables")
{ {
@ -442,17 +380,9 @@ TEST_CASE_FIXTURE(Fixture, "return_type_is_an_intersection_type_if_led_with_one_
AstTypeFunction* annotation = local->vars.data[0]->annotation->as<AstTypeFunction>(); AstTypeFunction* annotation = local->vars.data[0]->annotation->as<AstTypeFunction>();
REQUIRE(annotation != nullptr); REQUIRE(annotation != nullptr);
AstTypeIntersection* returnAnnotation;
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
auto returnTypePack = annotation->returnTypes->as<AstTypePackExplicit>(); auto returnTypePack = annotation->returnTypes->as<AstTypePackExplicit>();
REQUIRE(returnTypePack); REQUIRE(returnTypePack);
returnAnnotation = returnTypePack->typeList.types.data[0]->as<AstTypeIntersection>(); AstTypeIntersection* returnAnnotation = returnTypePack->typeList.types.data[0]->as<AstTypeIntersection>();
}
else
{
returnAnnotation = annotation->returnTypes_DEPRECATED.types.data[0]->as<AstTypeIntersection>();
}
REQUIRE(returnAnnotation != nullptr); REQUIRE(returnAnnotation != nullptr);
CHECK(returnAnnotation->types.data[0]->as<AstTypeGroup>()); CHECK(returnAnnotation->types.data[0]->as<AstTypeGroup>());
CHECK(returnAnnotation->types.data[1]->as<AstTypeFunction>()); CHECK(returnAnnotation->types.data[1]->as<AstTypeFunction>());
@ -2000,16 +1930,10 @@ TEST_CASE_FIXTURE(Fixture, "parse_declarations")
CHECK(func->name == "bar"); CHECK(func->name == "bar");
CHECK(func->nameLocation == Location({2, 25}, {2, 28})); CHECK(func->nameLocation == Location({2, 25}, {2, 28}));
REQUIRE_EQ(func->params.types.size, 1); REQUIRE_EQ(func->params.types.size, 1);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
auto retTypePack = func->retTypes->as<AstTypePackExplicit>(); auto retTypePack = func->retTypes->as<AstTypePackExplicit>();
REQUIRE(retTypePack); REQUIRE(retTypePack);
REQUIRE_EQ(retTypePack->typeList.types.size, 1); REQUIRE_EQ(retTypePack->typeList.types.size, 1);
}
else
{
REQUIRE_EQ(func->retTypes_DEPRECATED.types.size, 1);
}
AstStatDeclareFunction* varFunc = stat->body.data[2]->as<AstStatDeclareFunction>(); AstStatDeclareFunction* varFunc = stat->body.data[2]->as<AstStatDeclareFunction>();
REQUIRE(varFunc); REQUIRE(varFunc);
@ -2392,15 +2316,7 @@ TEST_CASE_FIXTURE(Fixture, "parse_variadics")
REQUIRE(fnFoo); REQUIRE(fnFoo);
CHECK_EQ(fnFoo->argTypes.types.size, 2); CHECK_EQ(fnFoo->argTypes.types.size, 2);
CHECK(fnFoo->argTypes.tailType); CHECK(fnFoo->argTypes.tailType);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
CHECK(fnFoo->returnTypes->is<AstTypePackVariadic>()); CHECK(fnFoo->returnTypes->is<AstTypePackVariadic>());
}
else
{
CHECK_EQ(fnFoo->returnTypes_DEPRECATED.types.size, 0);
CHECK(fnFoo->returnTypes_DEPRECATED.tailType);
}
AstStatTypeAlias* bar = stat->body.data[2]->as<AstStatTypeAlias>(); AstStatTypeAlias* bar = stat->body.data[2]->as<AstStatTypeAlias>();
REQUIRE(bar); REQUIRE(bar);
@ -2408,19 +2324,11 @@ TEST_CASE_FIXTURE(Fixture, "parse_variadics")
REQUIRE(fnBar); REQUIRE(fnBar);
CHECK_EQ(fnBar->argTypes.types.size, 0); CHECK_EQ(fnBar->argTypes.types.size, 0);
CHECK(!fnBar->argTypes.tailType); CHECK(!fnBar->argTypes.tailType);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
auto returnTypePack = fnBar->returnTypes->as<AstTypePackExplicit>(); auto returnTypePack = fnBar->returnTypes->as<AstTypePackExplicit>();
REQUIRE(returnTypePack); REQUIRE(returnTypePack);
CHECK_EQ(returnTypePack->typeList.types.size, 1); CHECK_EQ(returnTypePack->typeList.types.size, 1);
CHECK(returnTypePack->typeList.tailType); CHECK(returnTypePack->typeList.tailType);
} }
else
{
CHECK_EQ(fnBar->returnTypes_DEPRECATED.types.size, 1);
CHECK(fnBar->returnTypes_DEPRECATED.tailType);
}
}
TEST_CASE_FIXTURE(Fixture, "variadics_must_be_last") TEST_CASE_FIXTURE(Fixture, "variadics_must_be_last")
{ {
@ -2485,8 +2393,7 @@ TEST_CASE_FIXTURE(Fixture, "generic_pack_parsing")
REQUIRE(argAnnot != nullptr); REQUIRE(argAnnot != nullptr);
CHECK(argAnnot->genericName == "a"); CHECK(argAnnot->genericName == "a");
AstTypePackGeneric* retAnnot = FFlag::LuauStoreReturnTypesAsPackOnAst ? fnTy->returnTypes->as<AstTypePackGeneric>() AstTypePackGeneric* retAnnot = fnTy->returnTypes->as<AstTypePackGeneric>();
: fnTy->returnTypes_DEPRECATED.tailType->as<AstTypePackGeneric>();
REQUIRE(retAnnot != nullptr); REQUIRE(retAnnot != nullptr);
CHECK(retAnnot->genericName == "b"); CHECK(retAnnot->genericName == "b");
} }
@ -2571,9 +2478,7 @@ TEST_CASE_FIXTURE(Fixture, "function_type_named_arguments")
REQUIRE_EQ(func->argNames.size, 3); REQUIRE_EQ(func->argNames.size, 3);
REQUIRE(func->argNames.data[2]); REQUIRE(func->argNames.data[2]);
CHECK_EQ(func->argNames.data[2]->first, "c"); CHECK_EQ(func->argNames.data[2]->first, "c");
AstTypeFunction* funcRet = FFlag::LuauStoreReturnTypesAsPackOnAst AstTypeFunction* funcRet = func->returnTypes->as<AstTypePackExplicit>()->typeList.types.data[0]->as<AstTypeFunction>();
? func->returnTypes->as<AstTypePackExplicit>()->typeList.types.data[0]->as<AstTypeFunction>()
: func->returnTypes_DEPRECATED.types.data[0]->as<AstTypeFunction>();
REQUIRE(funcRet != nullptr); REQUIRE(funcRet != nullptr);
REQUIRE_EQ(funcRet->argTypes.types.size, 3); REQUIRE_EQ(funcRet->argTypes.types.size, 3);
REQUIRE_EQ(funcRet->argNames.size, 3); REQUIRE_EQ(funcRet->argNames.size, 3);
@ -2817,21 +2722,12 @@ TEST_CASE_FIXTURE(Fixture, "parse_return_type_ast_type_group")
auto funcType = alias1->type->as<AstTypeFunction>(); auto funcType = alias1->type->as<AstTypeFunction>();
REQUIRE(funcType); REQUIRE(funcType);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
auto returnTypePack = funcType->returnTypes->as<AstTypePackExplicit>(); auto returnTypePack = funcType->returnTypes->as<AstTypePackExplicit>();
REQUIRE(returnTypePack); REQUIRE(returnTypePack);
REQUIRE_EQ(1, returnTypePack->typeList.types.size); REQUIRE_EQ(1, returnTypePack->typeList.types.size);
REQUIRE(!returnTypePack->typeList.tailType); REQUIRE(!returnTypePack->typeList.tailType);
CHECK(returnTypePack->typeList.types.data[0]->is<AstTypeGroup>()); CHECK(returnTypePack->typeList.types.data[0]->is<AstTypeGroup>());
} }
else
{
REQUIRE_EQ(1, funcType->returnTypes_DEPRECATED.types.size);
REQUIRE(!funcType->returnTypes_DEPRECATED.tailType);
CHECK(funcType->returnTypes_DEPRECATED.types.data[0]->is<AstTypeGroup>());
}
}
TEST_CASE_FIXTURE(Fixture, "inner_and_outer_scope_of_functions_have_correct_end_position") TEST_CASE_FIXTURE(Fixture, "inner_and_outer_scope_of_functions_have_correct_end_position")
{ {
@ -2899,8 +2795,6 @@ TEST_CASE_FIXTURE(Fixture, "function_start_locations_are_before_attributes")
TEST_CASE_FIXTURE(Fixture, "for_loop_with_single_var_has_comma_positions_of_size_zero") TEST_CASE_FIXTURE(Fixture, "for_loop_with_single_var_has_comma_positions_of_size_zero")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
ParseOptions parseOptions; ParseOptions parseOptions;
parseOptions.storeCstData = true; parseOptions.storeCstData = true;

View file

@ -6,8 +6,6 @@
#include "doctest.h" #include "doctest.h"
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
using namespace Luau; using namespace Luau;
namespace namespace
@ -177,24 +175,13 @@ TEST_CASE_FIXTURE(RequireTracerFixture, "follow_typeof_in_return_type")
AstStatFunction* func = block->body.data[0]->as<AstStatFunction>(); AstStatFunction* func = block->body.data[0]->as<AstStatFunction>();
REQUIRE(func != nullptr); REQUIRE(func != nullptr);
AstTypeTypeof* typeofAnnotation;
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
AstTypePack* retAnnotation = func->func->returnAnnotation; AstTypePack* retAnnotation = func->func->returnAnnotation;
REQUIRE(retAnnotation); REQUIRE(retAnnotation);
AstTypePackExplicit* tp = retAnnotation->as<AstTypePackExplicit>(); AstTypePackExplicit* tp = retAnnotation->as<AstTypePackExplicit>();
REQUIRE(tp); REQUIRE(tp);
REQUIRE_EQ(tp->typeList.types.size, 1); REQUIRE_EQ(tp->typeList.types.size, 1);
typeofAnnotation = tp->typeList.types.data[0]->as<AstTypeTypeof>(); AstTypeTypeof* typeofAnnotation = tp->typeList.types.data[0]->as<AstTypeTypeof>();
}
else
{
std::optional<AstTypeList> retAnnotation = func->func->returnAnnotation_DEPRECATED;
REQUIRE(retAnnotation);
REQUIRE_EQ(retAnnotation->types.size, 1);
typeofAnnotation = retAnnotation->types.data[0]->as<AstTypeTypeof>();
}
REQUIRE(typeofAnnotation != nullptr); REQUIRE(typeofAnnotation != nullptr);
AstExprIndexName* indexName = typeofAnnotation->expr->as<AstExprIndexName>(); AstExprIndexName* indexName = typeofAnnotation->expr->as<AstExprIndexName>();

View file

@ -12,8 +12,6 @@
using namespace Luau; using namespace Luau;
LUAU_FASTFLAG(LuauStoreCSTData2)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
LUAU_FASTFLAG(LuauStoreLocalAnnotationColonPositions) LUAU_FASTFLAG(LuauStoreLocalAnnotationColonPositions)
LUAU_FASTFLAG(LuauCSTForReturnTypeFunctionTail) LUAU_FASTFLAG(LuauCSTForReturnTypeFunctionTail)
@ -49,7 +47,6 @@ TEST_CASE("string_literals_containing_utf8")
TEST_CASE("if_stmt_spaces_around_tokens") TEST_CASE("if_stmt_spaces_around_tokens")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string one = R"( if This then Once() end)"; const std::string one = R"( if This then Once() end)";
CHECK_EQ(one, transpile(one).code); CHECK_EQ(one, transpile(one).code);
@ -98,32 +95,16 @@ TEST_CASE("elseif_chains_indent_sensibly")
TEST_CASE("strips_type_annotations") TEST_CASE("strips_type_annotations")
{ {
const std::string code = R"( local s: string= 'hello there' )"; const std::string code = R"( local s: string= 'hello there' )";
if (FFlag::LuauStoreCSTData2)
{
const std::string expected = R"( local s = 'hello there' )"; const std::string expected = R"( local s = 'hello there' )";
CHECK_EQ(expected, transpile(code).code); CHECK_EQ(expected, transpile(code).code);
} }
else
{
const std::string expected = R"( local s = 'hello there' )";
CHECK_EQ(expected, transpile(code).code);
}
}
TEST_CASE("strips_type_assertion_expressions") TEST_CASE("strips_type_assertion_expressions")
{ {
const std::string code = R"( local s= some_function() :: any+ something_else() :: number )"; const std::string code = R"( local s= some_function() :: any+ something_else() :: number )";
if (FFlag::LuauStoreCSTData2)
{
const std::string expected = R"( local s= some_function() + something_else() )"; const std::string expected = R"( local s= some_function() + something_else() )";
CHECK_EQ(expected, transpile(code).code); CHECK_EQ(expected, transpile(code).code);
} }
else
{
const std::string expected = R"( local s= some_function() + something_else() )";
CHECK_EQ(expected, transpile(code).code);
}
}
TEST_CASE("function_taking_ellipsis") TEST_CASE("function_taking_ellipsis")
{ {
@ -149,7 +130,6 @@ TEST_CASE("for_loop")
TEST_CASE("for_loop_spaces_around_tokens") TEST_CASE("for_loop_spaces_around_tokens")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string one = R"( for index = 1, 10 do call(index) end )"; const std::string one = R"( for index = 1, 10 do call(index) end )";
CHECK_EQ(one, transpile(one).code); CHECK_EQ(one, transpile(one).code);
@ -174,7 +154,6 @@ TEST_CASE("for_in_loop")
TEST_CASE("for_in_loop_spaces_around_tokens") TEST_CASE("for_in_loop_spaces_around_tokens")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string one = R"( for k, v in ipairs(x) do end )"; const std::string one = R"( for k, v in ipairs(x) do end )";
CHECK_EQ(one, transpile(one).code); CHECK_EQ(one, transpile(one).code);
@ -193,7 +172,6 @@ TEST_CASE("for_in_loop_spaces_around_tokens")
TEST_CASE("for_in_single_variable") TEST_CASE("for_in_single_variable")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string one = R"( for key in pairs(x) do end )"; const std::string one = R"( for key in pairs(x) do end )";
CHECK_EQ(one, transpile(one).code); CHECK_EQ(one, transpile(one).code);
} }
@ -206,7 +184,6 @@ TEST_CASE("while_loop")
TEST_CASE("while_loop_spaces_around_tokens") TEST_CASE("while_loop_spaces_around_tokens")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string one = R"( while f(x) do print() end )"; const std::string one = R"( while f(x) do print() end )";
CHECK_EQ(one, transpile(one).code); CHECK_EQ(one, transpile(one).code);
@ -228,7 +205,6 @@ TEST_CASE("repeat_until_loop")
TEST_CASE("repeat_until_loop_condition_on_new_line") TEST_CASE("repeat_until_loop_condition_on_new_line")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string code = R"( const std::string code = R"(
repeat repeat
print() print()
@ -260,7 +236,6 @@ TEST_CASE("local_assignment")
TEST_CASE("local_assignment_spaces_around_tokens") TEST_CASE("local_assignment_spaces_around_tokens")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string one = R"( local x = 1 )"; const std::string one = R"( local x = 1 )";
CHECK_EQ(one, transpile(one).code); CHECK_EQ(one, transpile(one).code);
@ -294,7 +269,6 @@ TEST_CASE("local_function")
TEST_CASE("local_function_spaces_around_tokens") TEST_CASE("local_function_spaces_around_tokens")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string one = R"( local function p(o, m, ...) end )"; const std::string one = R"( local function p(o, m, ...) end )";
CHECK_EQ(one, transpile(one).code); CHECK_EQ(one, transpile(one).code);
@ -313,7 +287,6 @@ TEST_CASE("function")
TEST_CASE("function_spaces_around_tokens") TEST_CASE("function_spaces_around_tokens")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string two = R"( function p(o, m, ...) end )"; const std::string two = R"( function p(o, m, ...) end )";
CHECK_EQ(two, transpile(two).code); CHECK_EQ(two, transpile(two).code);
@ -342,8 +315,6 @@ TEST_CASE("function_spaces_around_tokens")
TEST_CASE("function_with_types_spaces_around_tokens") TEST_CASE("function_with_types_spaces_around_tokens")
{ {
ScopedFastFlag sffs[] = { ScopedFastFlag sffs[] = {
{FFlag::LuauStoreCSTData2, true},
{FFlag::LuauStoreReturnTypesAsPackOnAst, true},
{FFlag::LuauStoreLocalAnnotationColonPositions, true}, {FFlag::LuauStoreLocalAnnotationColonPositions, true},
}; };
std::string code = R"( function p<X, Y, Z...>(o: string, m: number, ...: any): string end )"; std::string code = R"( function p<X, Y, Z...>(o: string, m: number, ...: any): string end )";
@ -403,7 +374,6 @@ TEST_CASE("function_with_types_spaces_around_tokens")
TEST_CASE("returns_spaces_around_tokens") TEST_CASE("returns_spaces_around_tokens")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string one = R"( return 1 )"; const std::string one = R"( return 1 )";
CHECK_EQ(one, transpile(one).code); CHECK_EQ(one, transpile(one).code);
@ -416,7 +386,6 @@ TEST_CASE("returns_spaces_around_tokens")
TEST_CASE_FIXTURE(Fixture, "type_alias_spaces_around_tokens") TEST_CASE_FIXTURE(Fixture, "type_alias_spaces_around_tokens")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
std::string code = R"( type Foo = string )"; std::string code = R"( type Foo = string )";
CHECK_EQ(code, transpile(code, {}, true).code); CHECK_EQ(code, transpile(code, {}, true).code);
@ -465,7 +434,6 @@ TEST_CASE_FIXTURE(Fixture, "type_alias_spaces_around_tokens")
TEST_CASE_FIXTURE(Fixture, "type_alias_with_defaults_spaces_around_tokens") TEST_CASE_FIXTURE(Fixture, "type_alias_with_defaults_spaces_around_tokens")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
std::string code = R"( type Foo<X = string, Z... = ...any> = string )"; std::string code = R"( type Foo<X = string, Z... = ...any> = string )";
CHECK_EQ(code, transpile(code, {}, true).code); CHECK_EQ(code, transpile(code, {}, true).code);
@ -526,7 +494,6 @@ TEST_CASE("table_literal_closing_brace_at_correct_position")
TEST_CASE("table_literal_with_semicolon_separators") TEST_CASE("table_literal_with_semicolon_separators")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string code = R"( const std::string code = R"(
local t = { x = 1; y = 2 } local t = { x = 1; y = 2 }
)"; )";
@ -536,7 +503,6 @@ TEST_CASE("table_literal_with_semicolon_separators")
TEST_CASE("table_literal_with_trailing_separators") TEST_CASE("table_literal_with_trailing_separators")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string code = R"( const std::string code = R"(
local t = { x = 1, y = 2, } local t = { x = 1, y = 2, }
)"; )";
@ -546,7 +512,6 @@ TEST_CASE("table_literal_with_trailing_separators")
TEST_CASE("table_literal_with_spaces_around_separator") TEST_CASE("table_literal_with_spaces_around_separator")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string code = R"( const std::string code = R"(
local t = { x = 1 , y = 2 } local t = { x = 1 , y = 2 }
)"; )";
@ -556,7 +521,6 @@ TEST_CASE("table_literal_with_spaces_around_separator")
TEST_CASE("table_literal_with_spaces_around_equals") TEST_CASE("table_literal_with_spaces_around_equals")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string code = R"( const std::string code = R"(
local t = { x = 1 } local t = { x = 1 }
)"; )";
@ -566,7 +530,6 @@ TEST_CASE("table_literal_with_spaces_around_equals")
TEST_CASE("table_literal_multiline_with_indexers") TEST_CASE("table_literal_multiline_with_indexers")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string code = R"( const std::string code = R"(
local t = { local t = {
["my first value"] = "x"; ["my first value"] = "x";
@ -591,19 +554,9 @@ TEST_CASE("method_definitions")
TEST_CASE("spaces_between_keywords_even_if_it_pushes_the_line_estimation_off") TEST_CASE("spaces_between_keywords_even_if_it_pushes_the_line_estimation_off")
{ {
// Luau::Parser doesn't exactly preserve the string representation of numbers in Lua, so we can find ourselves
// falling out of sync with the original code. We need to push keywords out so that there's at least one space between them.
const std::string code = R"( if math.abs(raySlope) < .01 then return 0 end )"; const std::string code = R"( if math.abs(raySlope) < .01 then return 0 end )";
if (FFlag::LuauStoreCSTData2)
{
CHECK_EQ(code, transpile(code).code); CHECK_EQ(code, transpile(code).code);
} }
else
{
const std::string expected = R"( if math.abs(raySlope) < 0.01 then return 0 end)";
CHECK_EQ(expected, transpile(code).code);
}
}
TEST_CASE("numbers") TEST_CASE("numbers")
{ {
@ -614,34 +567,23 @@ TEST_CASE("numbers")
TEST_CASE("infinity") TEST_CASE("infinity")
{ {
const std::string code = R"( local a = 1e500 local b = 1e400 )"; const std::string code = R"( local a = 1e500 local b = 1e400 )";
if (FFlag::LuauStoreCSTData2)
{
CHECK_EQ(code, transpile(code).code); CHECK_EQ(code, transpile(code).code);
} }
else
{
const std::string expected = R"( local a = 1e500 local b = 1e500 )";
CHECK_EQ(expected, transpile(code).code);
}
}
TEST_CASE("numbers_with_separators") TEST_CASE("numbers_with_separators")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string code = R"( local a = 123_456_789 )"; const std::string code = R"( local a = 123_456_789 )";
CHECK_EQ(code, transpile(code).code); CHECK_EQ(code, transpile(code).code);
} }
TEST_CASE("hexadecimal_numbers") TEST_CASE("hexadecimal_numbers")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string code = R"( local a = 0xFFFF )"; const std::string code = R"( local a = 0xFFFF )";
CHECK_EQ(code, transpile(code).code); CHECK_EQ(code, transpile(code).code);
} }
TEST_CASE("binary_numbers") TEST_CASE("binary_numbers")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string code = R"( local a = 0b0101 )"; const std::string code = R"( local a = 0b0101 )";
CHECK_EQ(code, transpile(code).code); CHECK_EQ(code, transpile(code).code);
} }
@ -654,28 +596,24 @@ TEST_CASE("single_quoted_strings")
TEST_CASE("double_quoted_strings") TEST_CASE("double_quoted_strings")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string code = R"( local a = "hello world" )"; const std::string code = R"( local a = "hello world" )";
CHECK_EQ(code, transpile(code).code); CHECK_EQ(code, transpile(code).code);
} }
TEST_CASE("simple_interp_string") TEST_CASE("simple_interp_string")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string code = R"( local a = `hello world` )"; const std::string code = R"( local a = `hello world` )";
CHECK_EQ(code, transpile(code).code); CHECK_EQ(code, transpile(code).code);
} }
TEST_CASE("raw_strings") TEST_CASE("raw_strings")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string code = R"( local a = [[ hello world ]] )"; const std::string code = R"( local a = [[ hello world ]] )";
CHECK_EQ(code, transpile(code).code); CHECK_EQ(code, transpile(code).code);
} }
TEST_CASE("raw_strings_with_blocks") TEST_CASE("raw_strings_with_blocks")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string code = R"( local a = [==[ hello world ]==] )"; const std::string code = R"( local a = [==[ hello world ]==] )";
CHECK_EQ(code, transpile(code).code); CHECK_EQ(code, transpile(code).code);
} }
@ -694,7 +632,6 @@ TEST_CASE("escaped_strings_2")
TEST_CASE("escaped_strings_newline") TEST_CASE("escaped_strings_newline")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string code = R"( const std::string code = R"(
print("foo \ print("foo \
bar") bar")
@ -704,14 +641,12 @@ TEST_CASE("escaped_strings_newline")
TEST_CASE("escaped_strings_raw") TEST_CASE("escaped_strings_raw")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string code = R"( local x = [=[\v<((do|load)file|require)\s*\(?['"]\zs[^'"]+\ze['"]]=] )"; const std::string code = R"( local x = [=[\v<((do|load)file|require)\s*\(?['"]\zs[^'"]+\ze['"]]=] )";
CHECK_EQ(code, transpile(code).code); CHECK_EQ(code, transpile(code).code);
} }
TEST_CASE("position_correctly_updated_when_writing_multiline_string") TEST_CASE("position_correctly_updated_when_writing_multiline_string")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string code = R"( const std::string code = R"(
call([[ call([[
testing testing
@ -757,56 +692,48 @@ TEST_CASE("function_call_parentheses_multiple_args_no_space")
TEST_CASE("function_call_parentheses_multiple_args_space_before_commas") TEST_CASE("function_call_parentheses_multiple_args_space_before_commas")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string code = R"( call(arg1 ,arg3 ,arg3) )"; const std::string code = R"( call(arg1 ,arg3 ,arg3) )";
CHECK_EQ(code, transpile(code).code); CHECK_EQ(code, transpile(code).code);
} }
TEST_CASE("function_call_spaces_before_parentheses") TEST_CASE("function_call_spaces_before_parentheses")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string code = R"( call () )"; const std::string code = R"( call () )";
CHECK_EQ(code, transpile(code).code); CHECK_EQ(code, transpile(code).code);
} }
TEST_CASE("function_call_spaces_within_parentheses") TEST_CASE("function_call_spaces_within_parentheses")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string code = R"( call( ) )"; const std::string code = R"( call( ) )";
CHECK_EQ(code, transpile(code).code); CHECK_EQ(code, transpile(code).code);
} }
TEST_CASE("function_call_string_double_quotes") TEST_CASE("function_call_string_double_quotes")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string code = R"( call "string" )"; const std::string code = R"( call "string" )";
CHECK_EQ(code, transpile(code).code); CHECK_EQ(code, transpile(code).code);
} }
TEST_CASE("function_call_string_single_quotes") TEST_CASE("function_call_string_single_quotes")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string code = R"( call 'string' )"; const std::string code = R"( call 'string' )";
CHECK_EQ(code, transpile(code).code); CHECK_EQ(code, transpile(code).code);
} }
TEST_CASE("function_call_string_no_space") TEST_CASE("function_call_string_no_space")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string code = R"( call'string' )"; const std::string code = R"( call'string' )";
CHECK_EQ(code, transpile(code).code); CHECK_EQ(code, transpile(code).code);
} }
TEST_CASE("function_call_table_literal") TEST_CASE("function_call_table_literal")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string code = R"( call { x = 1 } )"; const std::string code = R"( call { x = 1 } )";
CHECK_EQ(code, transpile(code).code); CHECK_EQ(code, transpile(code).code);
} }
TEST_CASE("function_call_table_literal_no_space") TEST_CASE("function_call_table_literal_no_space")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string code = R"( call{x=1} )"; const std::string code = R"( call{x=1} )";
CHECK_EQ(code, transpile(code).code); CHECK_EQ(code, transpile(code).code);
} }
@ -851,7 +778,6 @@ TEST_CASE("emit_a_do_block_in_cases_of_potentially_ambiguous_syntax")
TEST_CASE_FIXTURE(Fixture, "parentheses_multiline") TEST_CASE_FIXTURE(Fixture, "parentheses_multiline")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
std::string code = R"( std::string code = R"(
local test = ( local test = (
x x
@ -863,9 +789,6 @@ local test = (
TEST_CASE_FIXTURE(Fixture, "stmt_semicolon") TEST_CASE_FIXTURE(Fixture, "stmt_semicolon")
{ {
ScopedFastFlag flags[] = {
{FFlag::LuauStoreCSTData2, true},
};
std::string code = R"( local test = 1; )"; std::string code = R"( local test = 1; )";
CHECK_EQ(code, transpile(code, {}, true).code); CHECK_EQ(code, transpile(code, {}, true).code);
@ -875,7 +798,6 @@ TEST_CASE_FIXTURE(Fixture, "stmt_semicolon")
TEST_CASE_FIXTURE(Fixture, "do_block_ending_with_semicolon") TEST_CASE_FIXTURE(Fixture, "do_block_ending_with_semicolon")
{ {
ScopedFastFlag sff{FFlag::LuauStoreCSTData2, true};
std::string code = R"( std::string code = R"(
do do
return; return;
@ -886,9 +808,6 @@ TEST_CASE_FIXTURE(Fixture, "do_block_ending_with_semicolon")
TEST_CASE_FIXTURE(Fixture, "if_stmt_semicolon") TEST_CASE_FIXTURE(Fixture, "if_stmt_semicolon")
{ {
ScopedFastFlag flags[] = {
{FFlag::LuauStoreCSTData2, true},
};
std::string code = R"( std::string code = R"(
if init then if init then
x = string.sub(x, utf8.offset(x, init)); x = string.sub(x, utf8.offset(x, init));
@ -899,9 +818,6 @@ TEST_CASE_FIXTURE(Fixture, "if_stmt_semicolon")
TEST_CASE_FIXTURE(Fixture, "if_stmt_semicolon_2") TEST_CASE_FIXTURE(Fixture, "if_stmt_semicolon_2")
{ {
ScopedFastFlag flags[] = {
{FFlag::LuauStoreCSTData2, true},
};
std::string code = R"( std::string code = R"(
if (t < 1) then return c/2*t*t + b end; if (t < 1) then return c/2*t*t + b end;
)"; )";
@ -910,9 +826,6 @@ TEST_CASE_FIXTURE(Fixture, "if_stmt_semicolon_2")
TEST_CASE_FIXTURE(Fixture, "for_loop_stmt_semicolon") TEST_CASE_FIXTURE(Fixture, "for_loop_stmt_semicolon")
{ {
ScopedFastFlag flags[] = {
{FFlag::LuauStoreCSTData2, true},
};
std::string code = R"( std::string code = R"(
for i,v in ... do for i,v in ... do
end; end;
@ -922,9 +835,6 @@ TEST_CASE_FIXTURE(Fixture, "for_loop_stmt_semicolon")
TEST_CASE_FIXTURE(Fixture, "while_do_semicolon") TEST_CASE_FIXTURE(Fixture, "while_do_semicolon")
{ {
ScopedFastFlag flags[] = {
{FFlag::LuauStoreCSTData2, true},
};
std::string code = R"( std::string code = R"(
while true do while true do
end; end;
@ -934,9 +844,6 @@ TEST_CASE_FIXTURE(Fixture, "while_do_semicolon")
TEST_CASE_FIXTURE(Fixture, "function_definition_semicolon") TEST_CASE_FIXTURE(Fixture, "function_definition_semicolon")
{ {
ScopedFastFlag flags[] = {
{FFlag::LuauStoreCSTData2, true},
};
std::string code = R"( std::string code = R"(
function foo() function foo()
end; end;
@ -1014,17 +921,8 @@ TEST_CASE("a_table_key_can_be_the_empty_string")
TEST_CASE("always_emit_a_space_after_local_keyword") TEST_CASE("always_emit_a_space_after_local_keyword")
{ {
std::string code = "do local aZZZZ = Workspace.P1.Shape local bZZZZ = Enum.PartType.Cylinder end"; std::string code = "do local aZZZZ = Workspace.P1.Shape local bZZZZ = Enum.PartType.Cylinder end";
if (FFlag::LuauStoreCSTData2)
{
CHECK_EQ(code, transpile(code).code); CHECK_EQ(code, transpile(code).code);
} }
else
{
std::string expected = "do local aZZZZ = Workspace.P1 .Shape local bZZZZ= Enum.PartType.Cylinder end";
CHECK_EQ(expected, transpile(code).code);
}
}
TEST_CASE_FIXTURE(Fixture, "types_should_not_be_considered_cyclic_if_they_are_not_recursive") TEST_CASE_FIXTURE(Fixture, "types_should_not_be_considered_cyclic_if_they_are_not_recursive")
{ {
@ -1060,23 +958,13 @@ TEST_CASE_FIXTURE(Fixture, "type_lists_should_be_emitted_correctly")
end end
)"; )";
std::string expected = FFlag::LuauStoreCSTData2 ? R"( std::string expected = R"(
local a:(a:string,b:number,...string)->(string,...number)=function(a:string,b:number,...:string): (string,...number) local a:(a:string,b:number,...string)->(string,...number)=function(a:string,b:number,...:string): (string,...number)
end end
local b:(...string)->(...number)=function(...:string): ...number local b:(...string)->(...number)=function(...:string): ...number
end end
local c:()->()=function(): ()
end
)"
: R"(
local a:(string,number,...string)->(string,...number)=function(a:string,b:number,...:string): (string,...number)
end
local b:(...string)->(...number)=function(...:string): ...number
end
local c:()->()=function(): () local c:()->()=function(): ()
end end
)"; )";
@ -1116,7 +1004,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_type_assertion")
TEST_CASE_FIXTURE(Fixture, "type_assertion_spaces_around_tokens") TEST_CASE_FIXTURE(Fixture, "type_assertion_spaces_around_tokens")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
std::string code = "local a = 5 :: number"; std::string code = "local a = 5 :: number";
CHECK_EQ(code, transpile(code, {}, true).code); CHECK_EQ(code, transpile(code, {}, true).code);
@ -1133,7 +1020,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_if_then_else")
TEST_CASE_FIXTURE(Fixture, "transpile_if_then_else_multiple_conditions") TEST_CASE_FIXTURE(Fixture, "transpile_if_then_else_multiple_conditions")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
std::string code = "local a = if 1 then 2 elseif 3 then 4 else 5"; std::string code = "local a = if 1 then 2 elseif 3 then 4 else 5";
CHECK_EQ(code, transpile(code).code); CHECK_EQ(code, transpile(code).code);
@ -1141,7 +1027,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_if_then_else_multiple_conditions")
TEST_CASE_FIXTURE(Fixture, "transpile_if_then_else_multiple_conditions_2") TEST_CASE_FIXTURE(Fixture, "transpile_if_then_else_multiple_conditions_2")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
std::string code = R"( std::string code = R"(
local x = if yes local x = if yes
then nil then nil
@ -1157,7 +1042,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_if_then_else_multiple_conditions_2")
TEST_CASE_FIXTURE(Fixture, "if_then_else_spaces_around_tokens") TEST_CASE_FIXTURE(Fixture, "if_then_else_spaces_around_tokens")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
std::string code = "local a = if 1 then 2 else 3"; std::string code = "local a = if 1 then 2 else 3";
CHECK_EQ(code, transpile(code).code); CHECK_EQ(code, transpile(code).code);
@ -1194,7 +1078,6 @@ TEST_CASE_FIXTURE(Fixture, "if_then_else_spaces_around_tokens")
TEST_CASE_FIXTURE(Fixture, "if_then_else_spaces_between_else_if") TEST_CASE_FIXTURE(Fixture, "if_then_else_spaces_between_else_if")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
std::string code = R"( std::string code = R"(
return return
if a then "was a" else if a then "was a" else
@ -1222,7 +1105,6 @@ local a: Import.Type
TEST_CASE_FIXTURE(Fixture, "transpile_type_reference_spaces_around_tokens") TEST_CASE_FIXTURE(Fixture, "transpile_type_reference_spaces_around_tokens")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
std::string code = R"( local _: Foo.Type )"; std::string code = R"( local _: Foo.Type )";
CHECK_EQ(code, transpile(code, {}, true).code); CHECK_EQ(code, transpile(code, {}, true).code);
@ -1251,7 +1133,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_type_reference_spaces_around_tokens")
TEST_CASE_FIXTURE(Fixture, "transpile_type_annotation_spaces_around_tokens") TEST_CASE_FIXTURE(Fixture, "transpile_type_annotation_spaces_around_tokens")
{ {
ScopedFastFlag sffs[] = { ScopedFastFlag sffs[] = {
{FFlag::LuauStoreCSTData2, true},
{FFlag::LuauStoreLocalAnnotationColonPositions, true}, {FFlag::LuauStoreLocalAnnotationColonPositions, true},
}; };
std::string code = R"( local _: Type )"; std::string code = R"( local _: Type )";
@ -1273,7 +1154,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_type_annotation_spaces_around_tokens")
TEST_CASE_FIXTURE(Fixture, "transpile_for_loop_annotation_spaces_around_tokens") TEST_CASE_FIXTURE(Fixture, "transpile_for_loop_annotation_spaces_around_tokens")
{ {
ScopedFastFlag sffs[] = { ScopedFastFlag sffs[] = {
{FFlag::LuauStoreCSTData2, true},
{FFlag::LuauStoreLocalAnnotationColonPositions, true}, {FFlag::LuauStoreLocalAnnotationColonPositions, true},
}; };
std::string code = R"( for i: number = 1, 10 do end )"; std::string code = R"( for i: number = 1, 10 do end )";
@ -1314,7 +1194,6 @@ local b: Packed<(number, string)>
TEST_CASE_FIXTURE(Fixture, "type_packs_spaces_around_tokens") TEST_CASE_FIXTURE(Fixture, "type_packs_spaces_around_tokens")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
std::string code = R"( type _ = Packed< T...> )"; std::string code = R"( type _ = Packed< T...> )";
CHECK_EQ(code, transpile(code, {}, true).code); CHECK_EQ(code, transpile(code, {}, true).code);
@ -1372,11 +1251,7 @@ TEST_CASE_FIXTURE(Fixture, "transpile_union_type_nested_2")
TEST_CASE_FIXTURE(Fixture, "transpile_union_type_nested_3") TEST_CASE_FIXTURE(Fixture, "transpile_union_type_nested_3")
{ {
std::string code = "local a: nil | (string & number)"; std::string code = "local a: nil | (string & number)";
if (FFlag::LuauStoreCSTData2)
CHECK_EQ(code, transpile(code, {}, true).code); CHECK_EQ(code, transpile(code, {}, true).code);
else
CHECK_EQ("local a: (string & number)?", transpile(code, {}, true).code);
} }
TEST_CASE_FIXTURE(Fixture, "transpile_intersection_type_nested") TEST_CASE_FIXTURE(Fixture, "transpile_intersection_type_nested")
@ -1395,10 +1270,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_intersection_type_nested_2")
TEST_CASE_FIXTURE(Fixture, "transpile_intersection_type_with_function") TEST_CASE_FIXTURE(Fixture, "transpile_intersection_type_with_function")
{ {
ScopedFastFlag flags[] = {
{FFlag::LuauStoreCSTData2, true},
{FFlag::LuauStoreReturnTypesAsPackOnAst, true},
};
std::string code = "type FnB<U...> = () -> U... & T"; std::string code = "type FnB<U...> = () -> U... & T";
CHECK_EQ(code, transpile(code, {}, true).code); CHECK_EQ(code, transpile(code, {}, true).code);
@ -1406,9 +1277,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_intersection_type_with_function")
TEST_CASE_FIXTURE(Fixture, "transpile_leading_union_pipe") TEST_CASE_FIXTURE(Fixture, "transpile_leading_union_pipe")
{ {
ScopedFastFlag flags[] = {
{FFlag::LuauStoreCSTData2, true},
};
std::string code = "local a: | string | number"; std::string code = "local a: | string | number";
CHECK_EQ(code, transpile(code, {}, true).code); CHECK_EQ(code, transpile(code, {}, true).code);
@ -1418,9 +1286,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_leading_union_pipe")
TEST_CASE_FIXTURE(Fixture, "transpile_union_spaces_around_tokens") TEST_CASE_FIXTURE(Fixture, "transpile_union_spaces_around_tokens")
{ {
ScopedFastFlag flags[] = {
{FFlag::LuauStoreCSTData2, true},
};
std::string code = "local a: string | number"; std::string code = "local a: string | number";
CHECK_EQ(code, transpile(code, {}, true).code); CHECK_EQ(code, transpile(code, {}, true).code);
@ -1430,9 +1295,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_union_spaces_around_tokens")
TEST_CASE_FIXTURE(Fixture, "transpile_leading_intersection_ampersand") TEST_CASE_FIXTURE(Fixture, "transpile_leading_intersection_ampersand")
{ {
ScopedFastFlag flags[] = {
{FFlag::LuauStoreCSTData2, true},
};
std::string code = "local a: & string & number"; std::string code = "local a: & string & number";
CHECK_EQ(code, transpile(code, {}, true).code); CHECK_EQ(code, transpile(code, {}, true).code);
@ -1442,9 +1304,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_leading_intersection_ampersand")
TEST_CASE_FIXTURE(Fixture, "transpile_intersection_spaces_around_tokens") TEST_CASE_FIXTURE(Fixture, "transpile_intersection_spaces_around_tokens")
{ {
ScopedFastFlag flags[] = {
{FFlag::LuauStoreCSTData2, true},
};
std::string code = "local a: string & number"; std::string code = "local a: string & number";
CHECK_EQ(code, transpile(code, {}, true).code); CHECK_EQ(code, transpile(code, {}, true).code);
@ -1454,9 +1313,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_intersection_spaces_around_tokens")
TEST_CASE_FIXTURE(Fixture, "transpile_mixed_union_intersection") TEST_CASE_FIXTURE(Fixture, "transpile_mixed_union_intersection")
{ {
ScopedFastFlag flags[] = {
{FFlag::LuauStoreCSTData2, true},
};
std::string code = "local a: string | (Foo & Bar)"; std::string code = "local a: string | (Foo & Bar)";
CHECK_EQ(code, transpile(code, {}, true).code); CHECK_EQ(code, transpile(code, {}, true).code);
@ -1481,9 +1337,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_mixed_union_intersection")
TEST_CASE_FIXTURE(Fixture, "transpile_preserve_union_optional_style") TEST_CASE_FIXTURE(Fixture, "transpile_preserve_union_optional_style")
{ {
ScopedFastFlag flags[] = {
{FFlag::LuauStoreCSTData2, true},
};
std::string code = "local a: string | nil"; std::string code = "local a: string | nil";
CHECK_EQ(code, transpile(code, {}, true).code); CHECK_EQ(code, transpile(code, {}, true).code);
@ -1515,7 +1368,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_varargs")
TEST_CASE_FIXTURE(Fixture, "index_name_spaces_around_tokens") TEST_CASE_FIXTURE(Fixture, "index_name_spaces_around_tokens")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
std::string one = "local _ = a.name"; std::string one = "local _ = a.name";
CHECK_EQ(one, transpile(one, {}, true).code); CHECK_EQ(one, transpile(one, {}, true).code);
@ -1528,7 +1380,6 @@ TEST_CASE_FIXTURE(Fixture, "index_name_spaces_around_tokens")
TEST_CASE_FIXTURE(Fixture, "index_name_ends_with_digit") TEST_CASE_FIXTURE(Fixture, "index_name_ends_with_digit")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
std::string code = "sparkles.Color = Color3.new()"; std::string code = "sparkles.Color = Color3.new()";
CHECK_EQ(code, transpile(code, {}, true).code); CHECK_EQ(code, transpile(code, {}, true).code);
} }
@ -1542,7 +1393,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_index_expr")
TEST_CASE_FIXTURE(Fixture, "index_expr_spaces_around_tokens") TEST_CASE_FIXTURE(Fixture, "index_expr_spaces_around_tokens")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
std::string one = "local _ = a[2]"; std::string one = "local _ = a[2]";
CHECK_EQ(one, transpile(one, {}, true).code); CHECK_EQ(one, transpile(one, {}, true).code);
@ -1586,7 +1436,6 @@ local _ = # e
TEST_CASE_FIXTURE(Fixture, "binary_spaces_around_tokens") TEST_CASE_FIXTURE(Fixture, "binary_spaces_around_tokens")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
std::string code = R"( std::string code = R"(
local _ = 1+1 local _ = 1+1
local _ = 1 +1 local _ = 1 +1
@ -1628,7 +1477,6 @@ a ..= ' - result'
TEST_CASE_FIXTURE(Fixture, "compound_assignment_spaces_around_tokens") TEST_CASE_FIXTURE(Fixture, "compound_assignment_spaces_around_tokens")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
std::string one = R"( a += 1 )"; std::string one = R"( a += 1 )";
CHECK_EQ(one, transpile(one, {}, true).code); CHECK_EQ(one, transpile(one, {}, true).code);
@ -1645,7 +1493,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_assign_multiple")
TEST_CASE_FIXTURE(Fixture, "transpile_assign_spaces_around_tokens") TEST_CASE_FIXTURE(Fixture, "transpile_assign_spaces_around_tokens")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
std::string one = "a = 1"; std::string one = "a = 1";
CHECK_EQ(one, transpile(one).code); CHECK_EQ(one, transpile(one).code);
@ -1681,11 +1528,7 @@ local f: <T,S...>(T, S...)->(number) = foo
TEST_CASE_FIXTURE(Fixture, "transpile_union_reverse") TEST_CASE_FIXTURE(Fixture, "transpile_union_reverse")
{ {
std::string code = "local a: nil | number"; std::string code = "local a: nil | number";
if (FFlag::LuauStoreCSTData2)
CHECK_EQ(code, transpile(code, {}, true).code); CHECK_EQ(code, transpile(code, {}, true).code);
else
CHECK_EQ("local a: number?", transpile(code, {}, true).code);
} }
TEST_CASE_FIXTURE(Fixture, "transpile_for_in_multiple") TEST_CASE_FIXTURE(Fixture, "transpile_for_in_multiple")
@ -1800,9 +1643,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_for_in_multiple_types")
TEST_CASE_FIXTURE(Fixture, "transpile_string_interp") TEST_CASE_FIXTURE(Fixture, "transpile_string_interp")
{ {
ScopedFastFlag fflags[] = {
{FFlag::LuauStoreCSTData2, true},
};
std::string code = R"( local _ = `hello {name}` )"; std::string code = R"( local _ = `hello {name}` )";
CHECK_EQ(code, transpile(code, {}, true).code); CHECK_EQ(code, transpile(code, {}, true).code);
@ -1810,9 +1650,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_string_interp")
TEST_CASE_FIXTURE(Fixture, "transpile_string_interp_multiline") TEST_CASE_FIXTURE(Fixture, "transpile_string_interp_multiline")
{ {
ScopedFastFlag fflags[] = {
{FFlag::LuauStoreCSTData2, true},
};
std::string code = R"( local _ = `hello { std::string code = R"( local _ = `hello {
name name
}!` )"; }!` )";
@ -1822,9 +1659,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_string_interp_multiline")
TEST_CASE_FIXTURE(Fixture, "transpile_string_interp_on_new_line") TEST_CASE_FIXTURE(Fixture, "transpile_string_interp_on_new_line")
{ {
ScopedFastFlag fflags[] = {
{FFlag::LuauStoreCSTData2, true},
};
std::string code = R"( std::string code = R"(
error( error(
`a {b} c` `a {b} c`
@ -1836,7 +1670,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_string_interp_on_new_line")
TEST_CASE_FIXTURE(Fixture, "transpile_string_interp_multiline_escape") TEST_CASE_FIXTURE(Fixture, "transpile_string_interp_multiline_escape")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
std::string code = R"( local _ = `hello \ std::string code = R"( local _ = `hello \
world!` )"; world!` )";
@ -1845,9 +1678,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_string_interp_multiline_escape")
TEST_CASE_FIXTURE(Fixture, "transpile_string_literal_escape") TEST_CASE_FIXTURE(Fixture, "transpile_string_literal_escape")
{ {
ScopedFastFlag fflags[] = {
{FFlag::LuauStoreCSTData2, true},
};
std::string code = R"( local _ = ` bracket = \{, backtick = \` = {'ok'} ` )"; std::string code = R"( local _ = ` bracket = \{, backtick = \` = {'ok'} ` )";
CHECK_EQ(code, transpile(code, {}, true).code); CHECK_EQ(code, transpile(code, {}, true).code);
@ -1862,7 +1692,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_type_functions")
TEST_CASE_FIXTURE(Fixture, "transpile_type_functions_spaces_around_tokens") TEST_CASE_FIXTURE(Fixture, "transpile_type_functions_spaces_around_tokens")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
std::string code = R"( type function foo() end )"; std::string code = R"( type function foo() end )";
CHECK_EQ(code, transpile(code, {}, true).code); CHECK_EQ(code, transpile(code, {}, true).code);
@ -1878,7 +1707,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_type_functions_spaces_around_tokens")
TEST_CASE_FIXTURE(Fixture, "transpile_typeof_spaces_around_tokens") TEST_CASE_FIXTURE(Fixture, "transpile_typeof_spaces_around_tokens")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
std::string code = R"( type X = typeof(x) )"; std::string code = R"( type X = typeof(x) )";
CHECK_EQ(code, transpile(code, {}, true).code); CHECK_EQ(code, transpile(code, {}, true).code);
@ -1903,14 +1731,12 @@ TEST_CASE("transpile_single_quoted_string_types")
TEST_CASE("transpile_double_quoted_string_types") TEST_CASE("transpile_double_quoted_string_types")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string code = R"( type a = "hello world" )"; const std::string code = R"( type a = "hello world" )";
CHECK_EQ(code, transpile(code, {}, true).code); CHECK_EQ(code, transpile(code, {}, true).code);
} }
TEST_CASE("transpile_raw_string_types") TEST_CASE("transpile_raw_string_types")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
std::string code = R"( type a = [[ hello world ]] )"; std::string code = R"( type a = [[ hello world ]] )";
CHECK_EQ(code, transpile(code, {}, true).code); CHECK_EQ(code, transpile(code, {}, true).code);
@ -1920,14 +1746,12 @@ TEST_CASE("transpile_raw_string_types")
TEST_CASE("transpile_escaped_string_types") TEST_CASE("transpile_escaped_string_types")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string code = R"( type a = "\\b\\t\\n\\\\" )"; const std::string code = R"( type a = "\\b\\t\\n\\\\" )";
CHECK_EQ(code, transpile(code, {}, true).code); CHECK_EQ(code, transpile(code, {}, true).code);
} }
TEST_CASE("transpile_type_table_semicolon_separators") TEST_CASE("transpile_type_table_semicolon_separators")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
const std::string code = R"( const std::string code = R"(
type Foo = { type Foo = {
bar: number; bar: number;
@ -1939,7 +1763,6 @@ TEST_CASE("transpile_type_table_semicolon_separators")
TEST_CASE("transpile_type_table_access_modifiers") TEST_CASE("transpile_type_table_access_modifiers")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
std::string code = R"( std::string code = R"(
type Foo = { type Foo = {
read bar: number, read bar: number,
@ -1960,7 +1783,6 @@ TEST_CASE("transpile_type_table_access_modifiers")
TEST_CASE("transpile_type_table_spaces_between_tokens") TEST_CASE("transpile_type_table_spaces_between_tokens")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
std::string code = R"( type Foo = { bar: number, } )"; std::string code = R"( type Foo = { bar: number, } )";
CHECK_EQ(code, transpile(code, {}, true).code); CHECK_EQ(code, transpile(code, {}, true).code);
@ -2003,7 +1825,6 @@ TEST_CASE("transpile_type_table_spaces_between_tokens")
TEST_CASE("transpile_type_table_preserve_original_indexer_style") TEST_CASE("transpile_type_table_preserve_original_indexer_style")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
std::string code = R"( std::string code = R"(
type Foo = { type Foo = {
[number]: string [number]: string
@ -2019,7 +1840,6 @@ TEST_CASE("transpile_type_table_preserve_original_indexer_style")
TEST_CASE("transpile_type_table_preserve_indexer_location") TEST_CASE("transpile_type_table_preserve_indexer_location")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
std::string code = R"( std::string code = R"(
type Foo = { type Foo = {
[number]: string, [number]: string,
@ -2048,7 +1868,6 @@ TEST_CASE("transpile_type_table_preserve_indexer_location")
TEST_CASE("transpile_type_table_preserve_property_definition_style") TEST_CASE("transpile_type_table_preserve_property_definition_style")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
std::string code = R"( std::string code = R"(
type Foo = { type Foo = {
["$$typeof1"]: string, ["$$typeof1"]: string,
@ -2060,7 +1879,6 @@ TEST_CASE("transpile_type_table_preserve_property_definition_style")
TEST_CASE("transpile_type_table_string_properties_spaces_between_tokens") TEST_CASE("transpile_type_table_string_properties_spaces_between_tokens")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
std::string code = R"( std::string code = R"(
type Foo = { type Foo = {
[ "$$typeof1"]: string, [ "$$typeof1"]: string,
@ -2072,10 +1890,6 @@ TEST_CASE("transpile_type_table_string_properties_spaces_between_tokens")
TEST_CASE("transpile_types_preserve_parentheses_style") TEST_CASE("transpile_types_preserve_parentheses_style")
{ {
ScopedFastFlag flags[] = {
{FFlag::LuauStoreCSTData2, true},
};
std::string code = R"( type Foo = number )"; std::string code = R"( type Foo = number )";
CHECK_EQ(code, transpile(code, {}, true).code); CHECK_EQ(code, transpile(code, {}, true).code);
@ -2113,10 +1927,6 @@ end
TEST_CASE("transpile_type_function_unnamed_arguments") TEST_CASE("transpile_type_function_unnamed_arguments")
{ {
ScopedFastFlag flags[] = {
{FFlag::LuauStoreCSTData2, true},
{FFlag::LuauStoreReturnTypesAsPackOnAst, true},
};
std::string code = R"( type Foo = () -> () )"; std::string code = R"( type Foo = () -> () )";
CHECK_EQ(code, transpile(code, {}, true).code); CHECK_EQ(code, transpile(code, {}, true).code);
@ -2150,10 +1960,6 @@ TEST_CASE("transpile_type_function_unnamed_arguments")
TEST_CASE("transpile_type_function_named_arguments") TEST_CASE("transpile_type_function_named_arguments")
{ {
ScopedFastFlag flags[] = {
{FFlag::LuauStoreCSTData2, true},
{FFlag::LuauStoreReturnTypesAsPackOnAst, true},
};
std::string code = R"( type Foo = (x: string) -> () )"; std::string code = R"( type Foo = (x: string) -> () )";
CHECK_EQ(code, transpile(code, {}, true).code); CHECK_EQ(code, transpile(code, {}, true).code);
@ -2181,10 +1987,6 @@ TEST_CASE("transpile_type_function_named_arguments")
TEST_CASE("transpile_type_function_generics") TEST_CASE("transpile_type_function_generics")
{ {
ScopedFastFlag flags[] = {
{FFlag::LuauStoreCSTData2, true},
{FFlag::LuauStoreReturnTypesAsPackOnAst, true},
};
std::string code = R"( type Foo = <X, Y, Z...>() -> () )"; std::string code = R"( type Foo = <X, Y, Z...>() -> () )";
CHECK_EQ(code, transpile(code, {}, true).code); CHECK_EQ(code, transpile(code, {}, true).code);
@ -2218,10 +2020,6 @@ TEST_CASE("transpile_type_function_generics")
TEST_CASE("transpile_type_function_return_types") TEST_CASE("transpile_type_function_return_types")
{ {
ScopedFastFlag fflags[] = {
{FFlag::LuauStoreCSTData2, true},
{FFlag::LuauStoreReturnTypesAsPackOnAst, true},
};
std::string code = R"( type Foo = () -> () )"; std::string code = R"( type Foo = () -> () )";
CHECK_EQ(code, transpile(code, {}, true).code); CHECK_EQ(code, transpile(code, {}, true).code);
@ -2268,8 +2066,6 @@ TEST_CASE("transpile_type_function_return_types")
TEST_CASE("transpile_chained_function_types") TEST_CASE("transpile_chained_function_types")
{ {
ScopedFastFlag fflags[] = { ScopedFastFlag fflags[] = {
{FFlag::LuauStoreCSTData2, true},
{FFlag::LuauStoreReturnTypesAsPackOnAst, true},
{FFlag::LuauCSTForReturnTypeFunctionTail, true}, {FFlag::LuauCSTForReturnTypeFunctionTail, true},
}; };
std::string code = R"( type Foo = () -> () -> () )"; std::string code = R"( type Foo = () -> () -> () )";
@ -2290,7 +2086,6 @@ TEST_CASE("fuzzer_nil_optional")
TEST_CASE("transpile_function_attributes") TEST_CASE("transpile_function_attributes")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
std::string code = R"( std::string code = R"(
@native @native
function foo() function foo()

View file

@ -18,6 +18,7 @@ LUAU_FASTFLAG(LuauEagerGeneralization4)
LUAU_FASTFLAG(LuauSimplifyOutOfLine2) LUAU_FASTFLAG(LuauSimplifyOutOfLine2)
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck2) LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck2)
LUAU_FASTFLAG(LuauErrorSuppressionTypeFunctionArgs) LUAU_FASTFLAG(LuauErrorSuppressionTypeFunctionArgs)
LUAU_FASTFLAG(LuauEmptyStringInKeyOf)
struct TypeFunctionFixture : Fixture struct TypeFunctionFixture : Fixture
{ {
@ -1706,6 +1707,31 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "error_suppression_should_work_on_type_functi
CHECK("Unknown type 'Colours'" == toString(result.errors[0])); CHECK("Unknown type 'Colours'" == toString(result.errors[0]));
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_should_not_assert_on_empty_string_props")
{
if (!FFlag::LuauSolverV2)
return;
ScopedFastFlag _{FFlag::LuauEmptyStringInKeyOf, true};
loadDefinition(R"(
declare class Foobar
one: boolean
[""]: number
end
)");
CheckResult results = check(R"(
export type FoobarKeys = keyof<Foobar>;
export type TableKeys = keyof<{ [""]: string, two: boolean }>
)");
LUAU_REQUIRE_NO_ERRORS(results);
CHECK_EQ(R"("" | "one")", toString(requireTypeAlias("FoobarKeys")));
CHECK_EQ(R"("" | "two")", toString(requireTypeAlias("TableKeys")));
}
struct TFFixture struct TFFixture
{ {
TypeArena arena_; TypeArena arena_;

View file

@ -14,6 +14,7 @@ LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck2)
LUAU_FASTFLAG(LuauUserTypeFunctionAliases) LUAU_FASTFLAG(LuauUserTypeFunctionAliases)
LUAU_FASTFLAG(LuauFollowTypeAlias) LUAU_FASTFLAG(LuauFollowTypeAlias)
LUAU_FASTFLAG(LuauFollowExistingTypeFunction) LUAU_FASTFLAG(LuauFollowExistingTypeFunction)
LUAU_FASTFLAG(LuauTypeFunctionSerializeFollowMetatable)
TEST_SUITE_BEGIN("UserDefinedTypeFunctionTests"); TEST_SUITE_BEGIN("UserDefinedTypeFunctionTests");
@ -2468,6 +2469,20 @@ end
)")); )"));
} }
TEST_CASE_FIXTURE(Fixture, "udtf_metatable_serialization_follows")
{
ScopedFastFlag luauTypeFunctionSerializeFollowMetatable{FFlag::LuauTypeFunctionSerializeFollowMetatable, true};
LUAU_REQUIRE_ERRORS(check(R"(
_ = setmetatable(_(),_),(_) or _ == _ or f
while _() do
export type function t0<O,I>(l0,l0)
end
type t39<A> = t0<{write [Vector3]:any},typeof(_),l0.t0,any>
end
)"));
}
TEST_CASE_FIXTURE(Fixture, "udtf_double_definition") TEST_CASE_FIXTURE(Fixture, "udtf_double_definition")
{ {
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};

View file

@ -7,8 +7,9 @@
#include "doctest.h" #include "doctest.h"
LUAU_FASTFLAG(LuauSolverV2); LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(DebugLuauMagicTypes); LUAU_FASTFLAG(DebugLuauMagicTypes)
LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps)
using namespace Luau; using namespace Luau;
@ -443,7 +444,10 @@ TEST_CASE_FIXTURE(Fixture, "self_referential_type_alias")
std::optional<Property> incr = get(oTable->props, "incr"); std::optional<Property> incr = get(oTable->props, "incr");
REQUIRE(incr); REQUIRE(incr);
const FunctionType* incrFunc = get<FunctionType>(incr->type()); if (FFlag::LuauRemoveTypeCallsForReadWriteProps)
REQUIRE(incr->readTy);
const FunctionType* incrFunc = FFlag::LuauRemoveTypeCallsForReadWriteProps ? get<FunctionType>(*incr->readTy) : get<FunctionType>(incr->type_DEPRECATED());
REQUIRE(incrFunc); REQUIRE(incrFunc);
std::optional<TypeId> firstArg = first(incrFunc->argTypes); std::optional<TypeId> firstArg = first(incrFunc->argTypes);
@ -602,7 +606,14 @@ TEST_CASE_FIXTURE(Fixture, "interface_types_belong_to_interface_arena")
TableType* exportsTable = getMutable<TableType>(*exportsType); TableType* exportsTable = getMutable<TableType>(*exportsType);
REQUIRE(exportsTable != nullptr); REQUIRE(exportsTable != nullptr);
TypeId n = exportsTable->props["n"].type(); TypeId n;
if (FFlag::LuauRemoveTypeCallsForReadWriteProps)
{
REQUIRE(exportsTable->props["n"].readTy);
n = *exportsTable->props["n"].readTy;
}
else
n = exportsTable->props["n"].type_DEPRECATED();
REQUIRE(n != nullptr); REQUIRE(n != nullptr);
CHECK(isInArena(n, mod.interfaceTypes)); CHECK(isInArena(n, mod.interfaceTypes));
@ -657,10 +668,24 @@ TEST_CASE_FIXTURE(Fixture, "cloned_interface_maintains_pointers_between_definiti
TableType* exportsTable = getMutable<TableType>(*exportsType); TableType* exportsTable = getMutable<TableType>(*exportsType);
REQUIRE(exportsTable != nullptr); REQUIRE(exportsTable != nullptr);
TypeId aType = exportsTable->props["a"].type(); TypeId aType;
if (FFlag::LuauRemoveTypeCallsForReadWriteProps)
{
REQUIRE(exportsTable->props["a"].readTy);
aType = *exportsTable->props["a"].readTy;
}
else
aType = exportsTable->props["a"].type_DEPRECATED();
REQUIRE(aType); REQUIRE(aType);
TypeId bType = exportsTable->props["b"].type(); TypeId bType;
if (FFlag::LuauRemoveTypeCallsForReadWriteProps)
{
REQUIRE(exportsTable->props["b"].readTy);
bType = *exportsTable->props["b"].readTy;
}
else
bType = exportsTable->props["b"].type_DEPRECATED();
REQUIRE(bType); REQUIRE(bType);
CHECK(isInArena(recordType, mod.interfaceTypes)); CHECK(isInArena(recordType, mod.interfaceTypes));

View file

@ -13,9 +13,10 @@
using namespace Luau; using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2); LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions) LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions)
LUAU_FASTFLAG(LuauDfgAllowUpdatesInLoops) LUAU_FASTFLAG(LuauDfgAllowUpdatesInLoops)
LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps)
TEST_SUITE_BEGIN("TypeInferAnyError"); TEST_SUITE_BEGIN("TypeInferAnyError");
@ -294,7 +295,13 @@ TEST_CASE_FIXTURE(Fixture, "assign_prop_to_table_by_calling_any_yields_any")
REQUIRE(ttv); REQUIRE(ttv);
REQUIRE(ttv->props.count("prop")); REQUIRE(ttv->props.count("prop"));
REQUIRE_EQ("any", toString(ttv->props["prop"].type())); if (FFlag::LuauRemoveTypeCallsForReadWriteProps)
{
REQUIRE(ttv->props["prop"].readTy);
CHECK_EQ("any", toString(*ttv->props["prop"].readTy));
}
else
REQUIRE_EQ("any", toString(ttv->props["prop"].type_DEPRECATED()));
} }
TEST_CASE_FIXTURE(Fixture, "quantify_any_does_not_bind_to_itself") TEST_CASE_FIXTURE(Fixture, "quantify_any_does_not_bind_to_itself")

View file

@ -14,7 +14,11 @@ LUAU_FASTFLAG(LuauTableCloneClonesType3)
LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauEagerGeneralization4)
LUAU_FASTFLAG(LuauArityMismatchOnUndersaturatedUnknownArguments) LUAU_FASTFLAG(LuauArityMismatchOnUndersaturatedUnknownArguments)
LUAU_FASTFLAG(LuauStringFormatImprovements) LUAU_FASTFLAG(LuauStringFormatImprovements)
LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps)
LUAU_FASTFLAG(LuauWriteOnlyPropertyMangling) LUAU_FASTFLAG(LuauWriteOnlyPropertyMangling)
LUAU_FASTFLAG(LuauEnableWriteOnlyProperties)
LUAU_FASTFLAG(LuauTableLiteralSubtypeCheckFunctionCalls)
LUAU_FASTFLAG(LuauTypeCheckerStricterIndexCheck)
TEST_SUITE_BEGIN("BuiltinTests"); TEST_SUITE_BEGIN("BuiltinTests");
@ -1313,7 +1317,14 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "no_persistent_typelevel_change")
REQUIRE(mathTy); REQUIRE(mathTy);
TableType* ttv = getMutable<TableType>(mathTy); TableType* ttv = getMutable<TableType>(mathTy);
REQUIRE(ttv); REQUIRE(ttv);
const FunctionType* ftv = get<FunctionType>(ttv->props["frexp"].type()); const FunctionType* ftv;
if (FFlag::LuauRemoveTypeCallsForReadWriteProps)
{
REQUIRE(ttv->props["frexp"].readTy);
ftv = get<FunctionType>(*ttv->props["frexp"].readTy);
}
else
ftv = get<FunctionType>(ttv->props["frexp"].type_DEPRECATED());
REQUIRE(ftv); REQUIRE(ftv);
auto original = ftv->level; auto original = ftv->level;
@ -1720,12 +1731,14 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "better_string_format_error_when_format_strin
TEST_CASE_FIXTURE(Fixture, "write_only_table_assertion") TEST_CASE_FIXTURE(Fixture, "write_only_table_assertion")
{ {
ScopedFastFlag _{FFlag::LuauWriteOnlyPropertyMangling, true}; ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true},
{FFlag::LuauEnableWriteOnlyProperties, true},
{FFlag::LuauWriteOnlyPropertyMangling, true},
{FFlag::LuauTableLiteralSubtypeCheckFunctionCalls, true},
};
// CLI-157307: This currently errors as we claim the literal is not a LUAU_REQUIRE_NO_ERRORS(check(R"(
// `{ write foo: number }`, which is wrong as every table literal is
// trivially a write-only table.
LUAU_REQUIRE_ERRORS(check(R"(
local function accept(t: { write foo: number }) local function accept(t: { write foo: number })
end end
@ -1733,4 +1746,42 @@ TEST_CASE_FIXTURE(Fixture, "write_only_table_assertion")
)")); )"));
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "read_refinements_on_persistent_tables_known_property_identity")
{
// This will not result in a real refinement, as we refine `bnot`, a function, to be truthy
LUAU_REQUIRE_NO_ERRORS(check(R"(
if bit32.bnot then
end
)"));
}
TEST_CASE_FIXTURE(BuiltinsFixture, "read_refinements_on_persistent_tables_unknown_property")
{
ScopedFastFlag _{FFlag::LuauTypeCheckerStricterIndexCheck, true};
CheckResult results = check(R"(
if bit32.scrambleEggs then
end
)");
LUAU_REQUIRE_ERROR_COUNT(1, results);
auto err = get<UnknownProperty>(results.errors[0]);
CHECK_EQ(err->key, "scrambleEggs");
CHECK_EQ(toString(err->table), "typeof(bit32)");
}
TEST_CASE_FIXTURE(BuiltinsFixture, "read_refinements_on_persistent_tables_known_property_narrow")
{
ScopedFastFlag _{FFlag::LuauSolverV2, true};
LUAU_REQUIRE_NO_ERRORS(check(R"(
local myutf8 = utf8
if myutf8.charpattern == "lol" then
local foobar = myutf8.charpattern
local _ = foobar
end
)"));
CHECK_EQ("\"lol\"", toString(requireTypeAtPosition(Position{4, 23})));
}
TEST_SUITE_END(); TEST_SUITE_END();

View file

@ -15,6 +15,7 @@ using namespace Luau;
using std::nullopt; using std::nullopt;
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauTableLiteralSubtypeCheckFunctionCalls)
TEST_SUITE_BEGIN("TypeInferExternTypes"); TEST_SUITE_BEGIN("TypeInferExternTypes");
@ -441,6 +442,8 @@ b.X = 2 -- real Vector2.X is also read-only
TEST_CASE_FIXTURE(ExternTypeFixture, "detailed_class_unification_error") TEST_CASE_FIXTURE(ExternTypeFixture, "detailed_class_unification_error")
{ {
ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeCheckFunctionCalls, true};
CheckResult result = check(R"( CheckResult result = check(R"(
local function foo(v) local function foo(v)
return v.X :: number + string.len(v.Y) return v.X :: number + string.len(v.Y)
@ -455,7 +458,10 @@ b(a)
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
{ {
CHECK("Type 'number' could not be converted into 'string'" == toString(result.errors.at(0))); const std::string expected = "Type 'Vector2' could not be converted into '{ read X: unknown, read Y: string }'; \n"
"this is because accessing `Y` results in `number` in the former type and `string` in the latter type, "
"and `number` is not a subtype of `string`";
CHECK_EQ(expected, toString(result.errors.at(0)));
} }
else else
{ {

View file

@ -11,6 +11,7 @@ using namespace Luau;
LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAG(LuauSimplifyOutOfLine2) LUAU_FASTFLAG(LuauSimplifyOutOfLine2)
LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps)
TEST_SUITE_BEGIN("DefinitionTests"); TEST_SUITE_BEGIN("DefinitionTests");
@ -170,10 +171,23 @@ TEST_CASE_FIXTURE(Fixture, "class_definitions_cannot_overload_non_function")
REQUIRE(!result.success); REQUIRE(!result.success);
CHECK_EQ(result.parseResult.errors.size(), 0); CHECK_EQ(result.parseResult.errors.size(), 0);
REQUIRE(bool(result.module)); REQUIRE(bool(result.module));
if (FFlag::LuauRemoveTypeCallsForReadWriteProps)
REQUIRE_EQ(result.module->errors.size(), 2);
else
REQUIRE_EQ(result.module->errors.size(), 1); REQUIRE_EQ(result.module->errors.size(), 1);
GenericError* ge = get<GenericError>(result.module->errors[0]); GenericError* ge = get<GenericError>(result.module->errors[0]);
REQUIRE(ge); REQUIRE(ge);
if (FFlag::LuauRemoveTypeCallsForReadWriteProps)
CHECK_EQ("Cannot overload read type of non-function class member 'X'", ge->message);
else
CHECK_EQ("Cannot overload non-function class member 'X'", ge->message); CHECK_EQ("Cannot overload non-function class member 'X'", ge->message);
if (FFlag::LuauRemoveTypeCallsForReadWriteProps)
{
GenericError* ge2 = get<GenericError>(result.module->errors[1]);
REQUIRE(ge2);
CHECK_EQ("Cannot overload write type of non-function class member 'X'", ge2->message);
}
} }
TEST_CASE_FIXTURE(Fixture, "class_definitions_cannot_extend_non_class") TEST_CASE_FIXTURE(Fixture, "class_definitions_cannot_extend_non_class")
@ -366,7 +380,14 @@ TEST_CASE_FIXTURE(Fixture, "definitions_symbols_are_generated_for_recursively_re
const auto& method = cls->props["myMethod"]; const auto& method = cls->props["myMethod"];
CHECK_EQ(method.documentationSymbol, "@test/globaltype/MyClass.myMethod"); CHECK_EQ(method.documentationSymbol, "@test/globaltype/MyClass.myMethod");
FunctionType* function = getMutable<FunctionType>(method.type()); FunctionType* function;
if (FFlag::LuauRemoveTypeCallsForReadWriteProps)
{
REQUIRE(method.readTy);
function = getMutable<FunctionType>(*method.readTy);
}
else
function = getMutable<FunctionType>(method.type_DEPRECATED());
REQUIRE(function); REQUIRE(function);
REQUIRE(function->definition.has_value()); REQUIRE(function->definition.has_value());

View file

@ -29,6 +29,7 @@ LUAU_FASTFLAG(LuauDoNotAddUpvalueTypesToLocalType)
LUAU_FASTFLAG(LuauSimplifyOutOfLine2) LUAU_FASTFLAG(LuauSimplifyOutOfLine2)
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck2) LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck2)
LUAU_FASTFLAG(LuauAvoidGenericsLeakingDuringFunctionCallCheck) LUAU_FASTFLAG(LuauAvoidGenericsLeakingDuringFunctionCallCheck)
LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps)
TEST_SUITE_BEGIN("TypeInferFunctions"); TEST_SUITE_BEGIN("TypeInferFunctions");
@ -190,7 +191,15 @@ TEST_CASE_FIXTURE(Fixture, "generalize_table_property")
const TableType* tt = get<TableType>(follow(t)); const TableType* tt = get<TableType>(follow(t));
REQUIRE(tt); REQUIRE(tt);
TypeId fooTy = tt->props.at("foo").type(); TypeId fooTy;
if (FFlag::LuauRemoveTypeCallsForReadWriteProps)
{
const Property& foo = tt->props.at("foo");
REQUIRE(foo.readTy);
fooTy = *foo.readTy;
}
else
fooTy = tt->props.at("foo").type_DEPRECATED();
CHECK("<a>(a) -> a" == toString(fooTy)); CHECK("<a>(a) -> a" == toString(fooTy));
} }
@ -235,7 +244,16 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "vararg_function_is_quantified")
REQUIRE(ttv); REQUIRE(ttv);
REQUIRE(ttv->props.count("f")); REQUIRE(ttv->props.count("f"));
TypeId k = ttv->props["f"].type();
TypeId k;
if (FFlag::LuauRemoveTypeCallsForReadWriteProps)
{
const Property& f = ttv->props["f"];
REQUIRE(f.readTy);
k = *f.readTy;
}
else
k = ttv->props["f"].type_DEPRECATED();
REQUIRE(k); REQUIRE(k);
} }

View file

@ -19,6 +19,7 @@ LUAU_FASTFLAG(LuauIntersectNotNil)
LUAU_FASTFLAG(LuauSubtypingCheckFunctionGenericCounts) LUAU_FASTFLAG(LuauSubtypingCheckFunctionGenericCounts)
LUAU_FASTFLAG(LuauReportSubtypingErrors) LUAU_FASTFLAG(LuauReportSubtypingErrors)
LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauEagerGeneralization4)
LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps)
using namespace Luau; using namespace Luau;
@ -1142,7 +1143,15 @@ TEST_CASE_FIXTURE(Fixture, "generic_table_method")
REQUIRE(tTable != nullptr); REQUIRE(tTable != nullptr);
REQUIRE(tTable->props.count("bar")); REQUIRE(tTable->props.count("bar"));
TypeId barType = tTable->props["bar"].type(); TypeId barType;
if (FFlag::LuauRemoveTypeCallsForReadWriteProps)
{
Property& bar = tTable->props["bar"];
REQUIRE(bar.readTy);
barType = *bar.readTy;
}
else
barType = tTable->props["bar"].type_DEPRECATED();
REQUIRE(barType != nullptr); REQUIRE(barType != nullptr);
const FunctionType* ftv = get<FunctionType>(follow(barType)); const FunctionType* ftv = get<FunctionType>(follow(barType));
@ -1177,7 +1186,15 @@ TEST_CASE_FIXTURE(Fixture, "correctly_instantiate_polymorphic_member_functions")
std::optional<Property> fooProp = get(t->props, "foo"); std::optional<Property> fooProp = get(t->props, "foo");
REQUIRE(bool(fooProp)); REQUIRE(bool(fooProp));
const FunctionType* foo = get<FunctionType>(follow(fooProp->type()));
const FunctionType* foo;
if (FFlag::LuauRemoveTypeCallsForReadWriteProps)
{
REQUIRE(fooProp->readTy);
foo = get<FunctionType>(follow(*fooProp->readTy));
}
else
foo = get<FunctionType>(follow(fooProp->type_DEPRECATED()));
REQUIRE(bool(foo)); REQUIRE(bool(foo));
std::optional<TypeId> ret_ = first(foo->retTypes); std::optional<TypeId> ret_ = first(foo->retTypes);
@ -1224,7 +1241,14 @@ TEST_CASE_FIXTURE(Fixture, "instantiate_cyclic_generic_function")
std::optional<Property> methodProp = get(argTable->props, "method"); std::optional<Property> methodProp = get(argTable->props, "method");
REQUIRE(bool(methodProp)); REQUIRE(bool(methodProp));
const FunctionType* methodFunction = get<FunctionType>(follow(methodProp->type())); const FunctionType* methodFunction;
if (FFlag::LuauRemoveTypeCallsForReadWriteProps)
{
REQUIRE(methodProp->readTy);
methodFunction = get<FunctionType>(follow(*methodProp->readTy));
}
else
methodFunction = get<FunctionType>(follow(methodProp->type_DEPRECATED()));
REQUIRE(methodFunction != nullptr); REQUIRE(methodFunction != nullptr);
std::optional<TypeId> methodArg = first(methodFunction->argTypes); std::optional<TypeId> methodArg = first(methodFunction->argTypes);

View file

@ -10,8 +10,8 @@
using namespace Luau; using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauNarrowIntersectionNevers)
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck2) LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck2)
LUAU_FASTFLAG(LuauRefineTablesWithReadType)
TEST_SUITE_BEGIN("IntersectionTypes"); TEST_SUITE_BEGIN("IntersectionTypes");
@ -1458,8 +1458,10 @@ TEST_CASE_FIXTURE(Fixture, "cli_80596_simplify_more_realistic_intersections")
TEST_CASE_FIXTURE(BuiltinsFixture, "narrow_intersection_nevers") TEST_CASE_FIXTURE(BuiltinsFixture, "narrow_intersection_nevers")
{ {
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; ScopedFastFlag sffs[] = {
ScopedFastFlag narrowIntersections{FFlag::LuauNarrowIntersectionNevers, true}; {FFlag::LuauSolverV2, true},
{FFlag::LuauRefineTablesWithReadType, true},
};
loadDefinition(R"( loadDefinition(R"(
declare class Player declare class Player
@ -1474,7 +1476,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "narrow_intersection_nevers")
end end
)")); )"));
CHECK_EQ("Player & { Character: ~(false?) }", toString(requireTypeAtPosition({3, 23}))); CHECK_EQ("Player & { read Character: ~(false?) }", toString(requireTypeAtPosition({3, 23})));
} }
TEST_SUITE_END(); TEST_SUITE_END();

View file

@ -13,7 +13,6 @@ using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(DebugLuauEqSatSimplification) LUAU_FASTFLAG(DebugLuauEqSatSimplification)
LUAU_FASTFLAG(LuauStoreCSTData2)
LUAU_FASTINT(LuauNormalizeCacheLimit) LUAU_FASTINT(LuauNormalizeCacheLimit)
LUAU_FASTINT(LuauTarjanChildLimit) LUAU_FASTINT(LuauTarjanChildLimit)
LUAU_FASTINT(LuauTypeInferIterationLimit) LUAU_FASTINT(LuauTypeInferIterationLimit)
@ -49,16 +48,7 @@ TEST_CASE_FIXTURE(Fixture, "typeguard_inference_incomplete")
end end
)"; )";
const std::string expected = FFlag::LuauStoreCSTData2 ? R"( const std::string expected = R"(
function f(a:{fn:()->(a,b...)}): ()
if type(a) == 'boolean' then
local a1:boolean=a
elseif a.fn() then
local a2:{fn:()->(a,b...)}=a
end
end
)"
: R"(
function f(a:{fn:()->(a,b...)}): () function f(a:{fn:()->(a,b...)}): ()
if type(a) == 'boolean' then if type(a) == 'boolean' then
local a1:boolean=a local a1:boolean=a
@ -68,16 +58,7 @@ TEST_CASE_FIXTURE(Fixture, "typeguard_inference_incomplete")
end end
)"; )";
const std::string expectedWithNewSolver = FFlag::LuauStoreCSTData2 ? R"( const std::string expectedWithNewSolver = R"(
function f(a:{fn:()->(unknown,...unknown)}): ()
if type(a) == 'boolean' then
local a1:{fn:()->(unknown,...unknown)}&boolean=a
elseif a.fn() then
local a2:{fn:()->(unknown,...unknown)}&(class|function|nil|number|string|thread|buffer|table)=a
end
end
)"
: R"(
function f(a:{fn:()->(unknown,...unknown)}): () function f(a:{fn:()->(unknown,...unknown)}): ()
if type(a) == 'boolean' then if type(a) == 'boolean' then
local a1:{fn:()->(unknown,...unknown)}&boolean=a local a1:{fn:()->(unknown,...unknown)}&boolean=a
@ -87,16 +68,7 @@ TEST_CASE_FIXTURE(Fixture, "typeguard_inference_incomplete")
end end
)"; )";
const std::string expectedWithEqSat = FFlag::LuauStoreCSTData2 ? R"( const std::string expectedWithEqSat = R"(
function f(a:{fn:()->(unknown,...unknown)}): ()
if type(a) == 'boolean' then
local a1:{fn:()->(unknown,...unknown)}&boolean=a
elseif a.fn() then
local a2:{fn:()->(unknown,...unknown)}&negate<boolean>=a
end
end
)"
: R"(
function f(a:{fn:()->(unknown,...unknown)}): () function f(a:{fn:()->(unknown,...unknown)}): ()
if type(a) == 'boolean' then if type(a) == 'boolean' then
local a1:{fn:()->(unknown,...unknown)}&boolean=a local a1:{fn:()->(unknown,...unknown)}&boolean=a

View file

@ -18,6 +18,7 @@ LUAU_FASTFLAG(LuauBetterCannotCallFunctionPrimitive)
LUAU_FASTFLAG(LuauTypeCheckerStricterIndexCheck) LUAU_FASTFLAG(LuauTypeCheckerStricterIndexCheck)
LUAU_FASTFLAG(LuauNormalizationIntersectTablesPreservesExternTypes) LUAU_FASTFLAG(LuauNormalizationIntersectTablesPreservesExternTypes)
LUAU_FASTFLAG(LuauAvoidDoubleNegation) LUAU_FASTFLAG(LuauAvoidDoubleNegation)
LUAU_FASTFLAG(LuauRefineTablesWithReadType)
using namespace Luau; using namespace Luau;
@ -509,6 +510,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "impossible_type_narrow_is_not_an_error")
TEST_CASE_FIXTURE(Fixture, "truthy_constraint_on_properties") TEST_CASE_FIXTURE(Fixture, "truthy_constraint_on_properties")
{ {
ScopedFastFlag _{FFlag::LuauRefineTablesWithReadType, true};
CheckResult result = check(R"( CheckResult result = check(R"(
local t: {x: number?} = {x = 1} local t: {x: number?} = {x = 1}
@ -530,8 +533,7 @@ TEST_CASE_FIXTURE(Fixture, "truthy_constraint_on_properties")
} }
else else
{ {
// CLI-115281 - Types produced by refinements don't always get simplified CHECK("{ read x: number, write x: number? }" == toString(requireTypeAtPosition({4, 23})));
CHECK("{ x: number? } & { x: ~(false?) }" == toString(requireTypeAtPosition({4, 23})));
} }
CHECK("number" == toString(requireTypeAtPosition({5, 26}))); CHECK("number" == toString(requireTypeAtPosition({5, 26})));
} }
@ -998,6 +1000,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "either_number_or_string")
TEST_CASE_FIXTURE(Fixture, "not_t_or_some_prop_of_t") TEST_CASE_FIXTURE(Fixture, "not_t_or_some_prop_of_t")
{ {
ScopedFastFlag _{FFlag::LuauRefineTablesWithReadType, true};
CheckResult result = check(R"( CheckResult result = check(R"(
local function f(t: {x: boolean}?) local function f(t: {x: boolean}?)
if not t or t.x then if not t or t.x then
@ -1010,8 +1014,14 @@ TEST_CASE_FIXTURE(Fixture, "not_t_or_some_prop_of_t")
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
{ {
// CLI-115281 Types produced by refinements do not consistently get simplified // CLI-115281 Types produced by refinements do not consistently get simplified: we are minting a type like:
CHECK_EQ("({ x: boolean } & { x: ~(false?) })?", toString(requireTypeAtPosition({3, 28}))); //
// intersect<{ x: boolean } | nil, { read x: ~(false?) } | false | nil>
//
// ... which we can't _quite_ refine into the type it ought to be:
//
// { write x: boolean, read x: true } | nil
CHECK_EQ("({ read x: ~(false?) } & { x: boolean })?", toString(requireTypeAtPosition({3, 28})));
} }
else else
CHECK_EQ("{| x: boolean |}?", toString(requireTypeAtPosition({3, 28}))); CHECK_EQ("{| x: boolean |}?", toString(requireTypeAtPosition({3, 28})));
@ -1244,7 +1254,10 @@ TEST_CASE_FIXTURE(Fixture, "apply_refinements_on_astexprindexexpr_whose_subscrip
TEST_CASE_FIXTURE(Fixture, "discriminate_from_truthiness_of_x") TEST_CASE_FIXTURE(Fixture, "discriminate_from_truthiness_of_x")
{ {
ScopedFastFlag _{FFlag::LuauAvoidDoubleNegation, true}; ScopedFastFlag sffs[] = {
{FFlag::LuauAvoidDoubleNegation, true},
{FFlag::LuauRefineTablesWithReadType, true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
type T = {tag: "missing", x: nil} | {tag: "exists", x: string} type T = {tag: "missing", x: nil} | {tag: "exists", x: string}
@ -1262,15 +1275,8 @@ TEST_CASE_FIXTURE(Fixture, "discriminate_from_truthiness_of_x")
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
{ {
// CLI-115281 Types produced by refinements do not consistently get CHECK(R"({ tag: "exists", x: string })" == toString(requireTypeAtPosition({5, 28})));
// simplified. Sometimes this is due to not refining at the correct CHECK(R"({ tag: "missing", x: nil })" == toString(requireTypeAtPosition({7, 28})));
// time, sometimes this is due to hitting the simplifier rather than
// normalization.
CHECK("{ tag: \"exists\", x: string } & { x: ~(false?) }" == toString(requireTypeAtPosition({5, 28})));
CHECK(
R"(({ tag: "exists", x: string } & { x: false? }) | ({ tag: "missing", x: nil } & { x: false? }))" ==
toString(requireTypeAtPosition({7, 28}))
);
} }
else else
{ {
@ -2684,9 +2690,35 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1451")
)")); )"));
} }
TEST_CASE_FIXTURE(RefinementExternTypeFixture, "cannot_call_a_function") TEST_CASE_FIXTURE(RefinementExternTypeFixture, "cannot_call_a_function_single")
{ {
ScopedFastFlag sff{FFlag::LuauSolverV2, true}; ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true},
{FFlag::LuauBetterCannotCallFunctionPrimitive, true},
{FFlag::LuauTypeCheckerStricterIndexCheck, true},
{FFlag::LuauRefineTablesWithReadType, true},
};
CheckResult result = check(R"(
local function invokeDisconnect(d: unknown)
if type(d) == "function" then
d()
end
end
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ("The type function is not precise enough for us to determine the appropriate result type of this call.", toString(result.errors[0]));
}
TEST_CASE_FIXTURE(RefinementExternTypeFixture, "cannot_call_a_function_union")
{
ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true},
{FFlag::LuauBetterCannotCallFunctionPrimitive, true},
{FFlag::LuauTypeCheckerStricterIndexCheck, true},
{FFlag::LuauRefineTablesWithReadType, true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
type Disconnectable = { type Disconnectable = {
@ -2704,35 +2736,16 @@ TEST_CASE_FIXTURE(RefinementExternTypeFixture, "cannot_call_a_function")
end end
)"); )");
if (FFlag::LuauTypeCheckerStricterIndexCheck)
{
LUAU_REQUIRE_ERROR_COUNT(2, result); LUAU_REQUIRE_ERROR_COUNT(2, result);
CHECK_EQ( // FIXME CLI-157125: It's a bit clowny that we return a union of
toString(result.errors[0]), // functions containing `function` here, but it looks like a side
"Key 'Disconnect' is missing from 't2 where t1 = ExternScriptConnection | t2 | { Disconnect: (t1) -> (...any) } ; t2 = { disconnect: " // effect of how we execute `hasProp`.
"(t1) -> (...any) }' in the type 't1 where t1 = ExternScriptConnection | { Disconnect: (t1) -> (...any) } | { disconnect: (t1) -> " std::string expectedError = "Cannot call a value of type function in union:\n"
"(...any) }'" " ((ExternScriptConnection) -> ()) | function | t2 where t1 = ExternScriptConnection | { Disconnect: t2 } | { "
); "disconnect: (t1) -> (...any) } ; t2 = (t1) -> (...any)";
if (FFlag::LuauBetterCannotCallFunctionPrimitive) CHECK_EQ(toString(result.errors[1]), expectedError);
CHECK_EQ(
toString(result.errors[1]), "The type function is not precise enough for us to determine the appropriate result type of this call."
);
else
CHECK_EQ(toString(result.errors[1]), "Cannot call a value of type function");
}
else
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauBetterCannotCallFunctionPrimitive)
CHECK_EQ(
toString(result.errors[0]), "The type function is not precise enough for us to determine the appropriate result type of this call."
);
else
CHECK_EQ(toString(result.errors[0]), "Cannot call a value of type function");
}
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1835") TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1835")

View file

@ -31,6 +31,11 @@ LUAU_FASTFLAG(LuauEnableWriteOnlyProperties)
LUAU_FASTFLAG(LuauDisablePrimitiveInferenceInLargeTables) LUAU_FASTFLAG(LuauDisablePrimitiveInferenceInLargeTables)
LUAU_FASTINT(LuauPrimitiveInferenceInTableLimit) LUAU_FASTINT(LuauPrimitiveInferenceInTableLimit)
LUAU_FASTFLAG(LuauAutocompleteMissingFollows) LUAU_FASTFLAG(LuauAutocompleteMissingFollows)
LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps)
LUAU_FASTFLAG(LuauRelateTablesAreNeverDisjoint)
LUAU_FASTFLAG(LuauTableLiteralSubtypeCheckFunctionCalls)
LUAU_FASTFLAG(LuauAvoidGenericsLeakingDuringFunctionCallCheck)
LUAU_FASTFLAG(LuauRefineTablesWithReadType)
TEST_SUITE_BEGIN("TableTests"); TEST_SUITE_BEGIN("TableTests");
@ -80,15 +85,33 @@ TEST_CASE_FIXTURE(Fixture, "basic")
std::optional<Property> fooProp = get(tType->props, "foo"); std::optional<Property> fooProp = get(tType->props, "foo");
REQUIRE(bool(fooProp)); REQUIRE(bool(fooProp));
CHECK_EQ(PrimitiveType::String, getPrimitiveType(fooProp->type())); if (FFlag::LuauRemoveTypeCallsForReadWriteProps)
{
REQUIRE(fooProp->readTy);
CHECK_EQ(PrimitiveType::String, getPrimitiveType(*fooProp->readTy));
}
else
CHECK_EQ(PrimitiveType::String, getPrimitiveType(fooProp->type_DEPRECATED()));
std::optional<Property> bazProp = get(tType->props, "baz"); std::optional<Property> bazProp = get(tType->props, "baz");
REQUIRE(bool(bazProp)); REQUIRE(bool(bazProp));
CHECK_EQ(PrimitiveType::Number, getPrimitiveType(bazProp->type())); if (FFlag::LuauRemoveTypeCallsForReadWriteProps)
{
REQUIRE(bazProp->readTy);
CHECK_EQ(PrimitiveType::Number, getPrimitiveType(*bazProp->readTy));
}
else
CHECK_EQ(PrimitiveType::Number, getPrimitiveType(bazProp->type_DEPRECATED()));
std::optional<Property> quuxProp = get(tType->props, "quux"); std::optional<Property> quuxProp = get(tType->props, "quux");
REQUIRE(bool(quuxProp)); REQUIRE(bool(quuxProp));
CHECK_EQ(PrimitiveType::NilType, getPrimitiveType(quuxProp->type())); if (FFlag::LuauRemoveTypeCallsForReadWriteProps)
{
REQUIRE(quuxProp->readTy);
CHECK_EQ(PrimitiveType::NilType, getPrimitiveType(*quuxProp->readTy));
}
else
CHECK_EQ(PrimitiveType::NilType, getPrimitiveType(quuxProp->type_DEPRECATED()));
} }
TEST_CASE_FIXTURE(Fixture, "augment_table") TEST_CASE_FIXTURE(Fixture, "augment_table")
@ -117,7 +140,16 @@ TEST_CASE_FIXTURE(Fixture, "augment_nested_table")
REQUIRE(tType != nullptr); REQUIRE(tType != nullptr);
REQUIRE(tType->props.find("p") != tType->props.end()); REQUIRE(tType->props.find("p") != tType->props.end());
const TableType* pType = get<TableType>(tType->props["p"].type());
const TableType* pType;
if (FFlag::LuauRemoveTypeCallsForReadWriteProps)
{
Property& p = tType->props["p"];
REQUIRE(p.readTy);
pType = get<TableType>(p.readTy);
}
else
pType = get<TableType>(tType->props["p"].type_DEPRECATED());
REQUIRE(pType != nullptr); REQUIRE(pType != nullptr);
CHECK("{ p: { foo: string } }" == toString(requireType("t"), {true})); CHECK("{ p: { foo: string } }" == toString(requireType("t"), {true}));
@ -261,7 +293,14 @@ TEST_CASE_FIXTURE(Fixture, "tc_member_function")
std::optional<Property> fooProp = get(tableType->props, "foo"); std::optional<Property> fooProp = get(tableType->props, "foo");
REQUIRE(bool(fooProp)); REQUIRE(bool(fooProp));
const FunctionType* methodType = get<FunctionType>(follow(fooProp->type())); const FunctionType* methodType;
if (FFlag::LuauRemoveTypeCallsForReadWriteProps)
{
REQUIRE(fooProp->readTy);
methodType = get<FunctionType>(follow(fooProp->readTy));
}
else
methodType = get<FunctionType>(follow(fooProp->type_DEPRECATED()));
REQUIRE(methodType != nullptr); REQUIRE(methodType != nullptr);
} }
@ -275,7 +314,15 @@ TEST_CASE_FIXTURE(Fixture, "tc_member_function_2")
std::optional<Property> uProp = get(tableType->props, "U"); std::optional<Property> uProp = get(tableType->props, "U");
REQUIRE(bool(uProp)); REQUIRE(bool(uProp));
TypeId uType = uProp->type();
TypeId uType;
if (FFlag::LuauRemoveTypeCallsForReadWriteProps)
{
REQUIRE(uProp->readTy);
uType = *uProp->readTy;
}
else
uType = uProp->type_DEPRECATED();
const TableType* uTable = get<TableType>(uType); const TableType* uTable = get<TableType>(uType);
REQUIRE(uTable != nullptr); REQUIRE(uTable != nullptr);
@ -283,7 +330,14 @@ TEST_CASE_FIXTURE(Fixture, "tc_member_function_2")
std::optional<Property> fooProp = get(uTable->props, "foo"); std::optional<Property> fooProp = get(uTable->props, "foo");
REQUIRE(bool(fooProp)); REQUIRE(bool(fooProp));
const FunctionType* methodType = get<FunctionType>(follow(fooProp->type())); const FunctionType* methodType;
if (FFlag::LuauRemoveTypeCallsForReadWriteProps)
{
REQUIRE(fooProp->readTy);
methodType = get<FunctionType>(follow(fooProp->readTy));
}
else
methodType = get<FunctionType>(follow(fooProp->type_DEPRECATED()));
REQUIRE(methodType != nullptr); REQUIRE(methodType != nullptr);
std::vector<TypeId> methodArgs = flatten(methodType->argTypes).first; std::vector<TypeId> methodArgs = flatten(methodType->argTypes).first;
@ -477,6 +531,8 @@ TEST_CASE_FIXTURE(Fixture, "table_param_width_subtyping_1")
TEST_CASE_FIXTURE(BuiltinsFixture, "table_param_width_subtyping_2") TEST_CASE_FIXTURE(BuiltinsFixture, "table_param_width_subtyping_2")
{ {
ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeCheckFunctionCalls, true};
CheckResult result = check(R"( CheckResult result = check(R"(
--!strict --!strict
function foo(o) function foo(o)
@ -489,24 +545,12 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_param_width_subtyping_2")
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
// CLI 114792 We don't report MissingProperties in many places where the old solver does
if (FFlag::LuauSolverV2)
{
TypeMismatch* error = get<TypeMismatch>(result.errors[0]);
REQUIRE_MESSAGE(error != nullptr, "Expected TypeMismatch but got " << toString(result.errors[0]));
CHECK("{ read bar: string }" == toString(error->givenType));
CHECK("{ read bar: string, read baz: string }" == toString(error->wantedType));
}
else
{
MissingProperties* error = get<MissingProperties>(result.errors[0]); MissingProperties* error = get<MissingProperties>(result.errors[0]);
REQUIRE_MESSAGE(error != nullptr, "Expected MissingProperties but got " << toString(result.errors[0])); REQUIRE_MESSAGE(error != nullptr, "Expected MissingProperties but got " << toString(result.errors[0]));
REQUIRE(error->properties.size() == 1); REQUIRE(error->properties.size() == 1);
CHECK_EQ("baz", error->properties[0]); CHECK_EQ("baz", error->properties[0]);
} }
}
TEST_CASE_FIXTURE(Fixture, "table_param_width_subtyping_3") TEST_CASE_FIXTURE(Fixture, "table_param_width_subtyping_3")
{ {
@ -1063,7 +1107,16 @@ TEST_CASE_FIXTURE(Fixture, "assigning_to_an_unsealed_table_with_string_literal_s
REQUIRE(tableType->indexer == std::nullopt); REQUIRE(tableType->indexer == std::nullopt);
REQUIRE(0 != tableType->props.count("a")); REQUIRE(0 != tableType->props.count("a"));
TypeId propertyA = tableType->props["a"].type();
TypeId propertyA;
if (FFlag::LuauRemoveTypeCallsForReadWriteProps)
{
Property& a = tableType->props["a"];
REQUIRE(a.readTy);
propertyA = *a.readTy;
}
else
propertyA = tableType->props["a"].type_DEPRECATED();
REQUIRE(propertyA != nullptr); REQUIRE(propertyA != nullptr);
CHECK_EQ(*getBuiltins()->stringType, *propertyA); CHECK_EQ(*getBuiltins()->stringType, *propertyA);
} }
@ -2298,8 +2351,12 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "quantifying_a_bound_var_works")
REQUIRE_MESSAGE(ttv, "Expected a table but got " << toString(ty, {true})); REQUIRE_MESSAGE(ttv, "Expected a table but got " << toString(ty, {true}));
REQUIRE(ttv->props.count("new")); REQUIRE(ttv->props.count("new"));
Property& prop = ttv->props["new"]; Property& prop = ttv->props["new"];
REQUIRE(prop.type()); if (FFlag::LuauRemoveTypeCallsForReadWriteProps)
const FunctionType* ftv = get<FunctionType>(follow(prop.type())); REQUIRE(prop.readTy);
else
REQUIRE(prop.type_DEPRECATED());
const FunctionType* ftv =
(FFlag::LuauRemoveTypeCallsForReadWriteProps) ? get<FunctionType>(follow(*prop.readTy)) : get<FunctionType>(follow(prop.type_DEPRECATED()));
REQUIRE(ftv); REQUIRE(ftv);
const TypePack* res = get<TypePack>(follow(ftv->retTypes)); const TypePack* res = get<TypePack>(follow(ftv->retTypes));
REQUIRE(res); REQUIRE(res);
@ -3156,7 +3213,15 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "dont_quantify_table_that_belongs_to_outer_sc
REQUIRE(counterType); REQUIRE(counterType);
REQUIRE(counterType->props.count("new")); REQUIRE(counterType->props.count("new"));
const FunctionType* newType = get<FunctionType>(follow(counterType->props["new"].type())); const FunctionType* newType;
if (FFlag::LuauRemoveTypeCallsForReadWriteProps)
{
Property& newProp = counterType->props["new"];
REQUIRE(newProp.readTy);
newType = get<FunctionType>(follow(*newProp.readTy));
}
else
newType = get<FunctionType>(follow(counterType->props["new"].type_DEPRECATED()));
REQUIRE(newType); REQUIRE(newType);
std::optional<TypeId> newRetType = *first(newType->retTypes); std::optional<TypeId> newRetType = *first(newType->retTypes);
@ -3517,6 +3582,8 @@ TEST_CASE_FIXTURE(Fixture, "dont_invalidate_the_properties_iterator_of_free_tabl
TEST_CASE_FIXTURE(Fixture, "checked_prop_too_early") TEST_CASE_FIXTURE(Fixture, "checked_prop_too_early")
{ {
ScopedFastFlag _{FFlag::LuauRefineTablesWithReadType, true};
CheckResult result = check(R"( CheckResult result = check(R"(
local t: {x: number?}? = {x = nil} local t: {x: number?}? = {x = nil}
local u = t.x and t or 5 local u = t.x and t or 5
@ -3527,7 +3594,7 @@ TEST_CASE_FIXTURE(Fixture, "checked_prop_too_early")
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
{ {
CHECK_EQ("Value of type '{ x: number? }?' could be nil", toString(result.errors[0])); CHECK_EQ("Value of type '{ x: number? }?' could be nil", toString(result.errors[0]));
CHECK_EQ("number | { x: number }", toString(requireType("u"))); CHECK_EQ("number | { read x: number, write x: number? }", toString(requireType("u")));
} }
else else
{ {
@ -3896,6 +3963,7 @@ TEST_CASE_FIXTURE(Fixture, "invariant_table_properties_means_instantiating_table
{ {
ScopedFastFlag sff[]{ ScopedFastFlag sff[]{
{FFlag::LuauInstantiateInSubtyping, true}, {FFlag::LuauInstantiateInSubtyping, true},
{FFlag::LuauTableLiteralSubtypeCheckFunctionCalls, true},
}; };
CheckResult result = check(R"( CheckResult result = check(R"(
@ -3915,12 +3983,12 @@ TEST_CASE_FIXTURE(Fixture, "invariant_table_properties_means_instantiating_table
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
{ {
// FIXME. We really should be reporting just one error in this case. CLI-114509 LUAU_REQUIRE_ERROR_COUNT(1, result);
LUAU_REQUIRE_ERROR_COUNT(3, result); auto err = get<TypeMismatch>(result.errors[0]);
CHECK_EQ(result.errors[0].location, Location{{10, 10}, {10, 11}});
CHECK(get<TypePackMismatch>(result.errors[0])); REQUIRE(err);
CHECK(get<TypeMismatch>(result.errors[1])); CHECK_EQ("{ m: (number) -> number }", toString(err->wantedType));
CHECK(get<TypeMismatch>(result.errors[2])); CHECK_EQ("{ m: <T>(T) -> T }", toString(err->givenType, {true}));
} }
else else
{ {
@ -4457,6 +4525,8 @@ TEST_CASE_FIXTURE(Fixture, "new_solver_supports_read_write_properties")
TEST_CASE_FIXTURE(Fixture, "table_subtyping_error_suppression") TEST_CASE_FIXTURE(Fixture, "table_subtyping_error_suppression")
{ {
ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeCheckFunctionCalls, true};
CheckResult result = check(R"( CheckResult result = check(R"(
function one(tbl: {x: any}) end function one(tbl: {x: any}) end
function two(tbl: {x: string}) one(tbl) end -- ok, string <: any and any <: string function two(tbl: {x: string}) one(tbl) end -- ok, string <: any and any <: string
@ -4476,8 +4546,8 @@ TEST_CASE_FIXTURE(Fixture, "table_subtyping_error_suppression")
// honestly not sure which of these is a better developer experience. // honestly not sure which of these is a better developer experience.
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
{ {
CHECK_EQ(*tm->wantedType, *getBuiltins()->stringType); CHECK_EQ("{ x: any, y: string }", toString(tm->wantedType));
CHECK_EQ(*tm->givenType, *getBuiltins()->numberType); CHECK_EQ("{ x: string, y: number }", toString(tm->givenType));
} }
else else
{ {
@ -5583,7 +5653,7 @@ TEST_CASE_FIXTURE(Fixture, "unsafe_bidirectional_mutation")
{ {
// It's kind of suspect that we allow multiple definitions of keys in // It's kind of suspect that we allow multiple definitions of keys in
// a single table. // a single table.
LUAU_REQUIRE_NO_ERRORS(check(R"( CheckResult result = check(R"(
type F = { type F = {
_G: () -> () _G: () -> ()
} }
@ -5595,7 +5665,9 @@ TEST_CASE_FIXTURE(Fixture, "unsafe_bidirectional_mutation")
_G = {}, _G = {},
_G = _, _G = _,
}) })
)")); )");
LUAU_REQUIRE_NO_ERRORS(result);
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "function_call_in_indexer_with_compound_assign") TEST_CASE_FIXTURE(BuiltinsFixture, "function_call_in_indexer_with_compound_assign")
@ -5643,6 +5715,7 @@ TEST_CASE_FIXTURE(Fixture, "fuzz_match_literal_type_crash_again")
} }
) )
)"); )");
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
} }
@ -5847,4 +5920,167 @@ TEST_CASE_FIXTURE(Fixture, "oss_1859")
CHECK_EQ("{ actions: { meow: (...any) -> string }, age: number, name: string }", toString(err->givenType)); CHECK_EQ("{ actions: { meow: (...any) -> string }, age: number, name: string }", toString(err->givenType));
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1797_intersection_of_tables_arent_disjoint")
{
ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true},
{FFlag::LuauRelateTablesAreNeverDisjoint, true},
{FFlag::LuauRefineTablesWithReadType, true},
};
LUAU_REQUIRE_NO_ERRORS(check(R"(
--!strict
export type Foo = {
foo: string,
}
export type Bar = Foo & {
copy: (...any) -> any
}
local function _test(nd: { bar: Bar? })
local bar = nd.bar
if not bar then
return
end
print(bar)
end
)"));
// This might be better as the expanded intersection, but the point of
// this test is that this _isn't_ `never`.
CHECK_EQ("Foo & { copy: (...any) -> any }", toString(requireTypeAtPosition({16, 20})));
}
TEST_CASE_FIXTURE(Fixture, "oss_1344")
{
ScopedFastFlag _{FFlag::LuauRefineTablesWithReadType, true};
LUAU_REQUIRE_NO_ERRORS(check(R"(
--!strict
type t = {
value: string?,
}
local t: t = {}
if not t.value then
t.value = ""
end
local s: string? = nil
if not s then
s = ""
end
)"));
}
TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1651")
{
ScopedFastFlag _{FFlag::LuauRefineTablesWithReadType, true};
LUAU_REQUIRE_NO_ERRORS(check(R"(
--!strict
local MyModule = {}
MyModule._isEnabled = true :: boolean
assert(MyModule._isEnabled, `type solver`)
MyModule._isEnabled = false
)"));
}
TEST_CASE_FIXTURE(Fixture, "narrow_table_literal_check_call")
{
ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true},
{FFlag::LuauTableLiteralSubtypeCheckFunctionCalls, true},
};
LUAU_REQUIRE_NO_ERRORS(check(R"(
local function take(_: { foo: string? }) end
take({ foo = "bar" })
)"));
}
TEST_CASE_FIXTURE(Fixture, "narrow_table_literal_check_call_incorrect")
{
ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true},
{FFlag::LuauTableLiteralSubtypeCheckFunctionCalls, true},
};
CheckResult results = check(R"(
local function take(_: { foo: string?, bing: number }) end
take({ foo = "bar", bing = true })
)");
LUAU_REQUIRE_ERROR_COUNT(1, results);
auto err = get<TypeMismatch>(results.errors[0]);
CHECK_EQ(results.errors[0].location, Location{{3, 35}, {3, 39}});
CHECK_EQ(toString(err->givenType), "boolean");
CHECK_EQ(toString(err->wantedType), "number");
}
TEST_CASE_FIXTURE(Fixture, "narrow_table_literal_check_call_singleton")
{
ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true},
{FFlag::LuauTableLiteralSubtypeCheckFunctionCalls, true},
};
CheckResult results = check(R"(
local function take(_: { foo: "foo" }) end
take({ foo = "foo" })
)");
LUAU_REQUIRE_NO_ERRORS(results);
}
TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1450")
{
ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true},
{FFlag::LuauAvoidGenericsLeakingDuringFunctionCallCheck, true},
{FFlag::LuauTableLiteralSubtypeCheckFunctionCalls, true},
{FFlag::LuauEagerGeneralization4, true},
};
CheckResult results = check(R"(
local keycodes = {
Alt = 2,
Space = 3,
Tab = 4,
}
type Keycode = keyof<typeof(keycodes)>
local function sendInput(keycodes: { Keycode })
print(keycodes)
end
sendInput({"Alt"}) -- shouldn't error
sendInput(
{
"Alt",
"Space",
"Ctrl", -- should error
}
)
)");
LUAU_REQUIRE_ERROR_COUNT(1, results);
auto err = get<TypeMismatch>(results.errors[0]);
REQUIRE(err);
// NOTE: Single line with just `"Ctrl"`
CHECK_EQ(results.errors[0].location, Location{{17, 16}, {17, 22}});
CHECK_EQ(R"("Alt" | "Space" | "Tab")", toString(err->wantedType));
// FIXME: CLI-157899
CHECK_EQ(R"("Alt" | "Ctrl" | "Space" | "Tab")", toString(err->givenType));
}
TEST_SUITE_END(); TEST_SUITE_END();

View file

@ -2443,4 +2443,15 @@ TEST_CASE_FIXTURE(Fixture, "fuzzer_infer_divergent_rw_props")
)")); )"));
} }
TEST_CASE_FIXTURE(Fixture, "read_table_type_refinements_persist_scope")
{
ScopedFastFlag _{FFlag::LuauSolverV2, true};
LUAU_REQUIRE_ERRORS(check(R"(
_ = {n0=_,},if _._ then ... else if _[if _ then _ else ({nil,})].setmetatable then if _ then _ elseif l0 then ... elseif _.n0 then _ elseif function<A>(l0)
return _._G,_
end then _._G else ...
)"));
}
TEST_SUITE_END(); TEST_SUITE_END();

View file

@ -93,7 +93,7 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "tables_can_be_unified")
TableType{{{"foo", {arena.freshType(getBuiltins(), globalScope->level)}}}, std::nullopt, globalScope->level, TableState::Unsealed}, TableType{{{"foo", {arena.freshType(getBuiltins(), globalScope->level)}}}, std::nullopt, globalScope->level, TableState::Unsealed},
}}; }};
CHECK_NE(*getMutable<TableType>(&tableOne)->props["foo"].type(), *getMutable<TableType>(&tableTwo)->props["foo"].type()); CHECK_NE(*getMutable<TableType>(&tableOne)->props["foo"].type_DEPRECATED(), *getMutable<TableType>(&tableTwo)->props["foo"].type_DEPRECATED());
state.tryUnify(&tableTwo, &tableOne); state.tryUnify(&tableTwo, &tableOne);
@ -102,7 +102,7 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "tables_can_be_unified")
state.log.commit(); state.log.commit();
CHECK_EQ(*getMutable<TableType>(&tableOne)->props["foo"].type(), *getMutable<TableType>(&tableTwo)->props["foo"].type()); CHECK_EQ(*getMutable<TableType>(&tableOne)->props["foo"].type_DEPRECATED(), *getMutable<TableType>(&tableTwo)->props["foo"].type_DEPRECATED());
} }
TEST_CASE_FIXTURE(TryUnifyFixture, "incompatible_tables_are_preserved") TEST_CASE_FIXTURE(TryUnifyFixture, "incompatible_tables_are_preserved")
@ -125,14 +125,14 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "incompatible_tables_are_preserved")
}, },
}}; }};
CHECK_NE(*getMutable<TableType>(&tableOne)->props["foo"].type(), *getMutable<TableType>(&tableTwo)->props["foo"].type()); CHECK_NE(*getMutable<TableType>(&tableOne)->props["foo"].type_DEPRECATED(), *getMutable<TableType>(&tableTwo)->props["foo"].type_DEPRECATED());
state.tryUnify(&tableTwo, &tableOne); state.tryUnify(&tableTwo, &tableOne);
CHECK(state.failure); CHECK(state.failure);
CHECK_EQ(1, state.errors.size()); CHECK_EQ(1, state.errors.size());
CHECK_NE(*getMutable<TableType>(&tableOne)->props["foo"].type(), *getMutable<TableType>(&tableTwo)->props["foo"].type()); CHECK_NE(*getMutable<TableType>(&tableOne)->props["foo"].type_DEPRECATED(), *getMutable<TableType>(&tableTwo)->props["foo"].type_DEPRECATED());
} }
TEST_CASE_FIXTURE(Fixture, "uninhabited_intersection_sub_never") TEST_CASE_FIXTURE(Fixture, "uninhabited_intersection_sub_never")