Compare commits

...

3 commits

Author SHA1 Message Date
ariel
72f6c8b679
Sync to upstream/release/672 (#1800)
# What's Changed?

Hi there, folks! It's been another busy week in the type mines, trying
to bring you all the very best type inference system we can. We've got a
bunch of updates to large pain points across the new type solver, and
our next big update (currently under a debug flag) improving type
generalization is finally nearing completion (and should hopefully
eliminate quite a lot of "type solver failed to complete" errors). We've
also continued polishing both the CST Parser and the `Luau.Require`
library we introduced a few releases ago based on user feedback and bug
reports, and we're really happy with how they're turning out.

# Parser
- Fixes a bug in the CST tooling where the spacing on return type
annotations for functions was not being printed correctly.
- Resolves some issues with the JSON encoding of `AstGenericType` and
`AstGenericTypePack`

# Runtime
- Implements support for yielding requires in `Luau.Require` library.
- Improves the error messages for require-by-string to include the chunk
name that was problematic where possible and the overall require path
that failed to be required.
- Fixes a bug that prevented the use of `require` within C functions and
`pcall`.
- Adds an API to support selectively removing chunks from the require
cache in `Luau.Require`
- Adds an API to support clearing the entire require cache in
`Luau.Require`

# New Type Solver

- Fixes a crash in the new non-strict mode when visiting function return
types in incomplete ASTs (e.g. during editing).
- Improves type simplification to support intersections of tables with
extern types, resolving _one_ of the causes of frequent refinements
unexpectedly leading to `never`.
- Improves type inference to better understand diverging branches in
functions, reducing false negatives where the type system fails to learn
that a binding must now always be initialized.
- Fixes a typo in the type definitions for user-defined function types
where the `intersection` tag was misspelled.
- Improves the overall accuracy of free type tracking during constraint
solving, leading to better inference results overall.
- Implements `types.optional` as a new library function for user-defined
type functions to make it easier to union a type with `nil`.
- Resolves a number of bugs caused by local type inference expanding the
domain of upvalues

# Internal Contributors

Co-authored-by: Andy Friesen <afriesen@roblox.com>
Co-authored-by: Ariel Weiss <aaronweiss@roblox.com>
Co-authored-by: Hunter Goldstein <hgoldstein@roblox.com>
Co-authored-by: Talha Pathan <tpathan@roblox.com>
Co-authored-by: Varun Saini <vsaini@roblox.com>
Co-authored-by: Vighnesh Vijay <vvijay@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: Menarul Alam <malam@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: Andy Friesen <afriesen@roblox.com>
2025-05-02 14:00:23 -07:00
Varun Saini
d9aa88e772
Fix crash when require is called from root VM stack (#1788)
Copied from #1785:
> If require is called from the root interpreter stack (e.g. using C
API) then lua_getinfo call will not succeed, leaving garbage in
lua_Debug ar struct.
> Accessing later ar.source as null-terminated string is unsafe and can
cause a crash.
> 
> This PR adds a check to ensure that lua_getinfo call is successful.

Co-authored-by: Alex Orlenko <zxteam@protonmail.com>
2025-04-28 11:15:43 -07:00
Andy Friesen
c51743268b
Sync to upstream/release/671 (#1787)
# General

* Internally rename `ClassType` to `ExternType`. In definition files,
the syntax to define these types has changed to `declare extern type Foo
with prop: type end`
* Add `luarequire_registermodule` to Luau.Require
* Support yieldable Luau C functions calling other functions
* Store return types as `AstTypePack*` on Ast nodes

## New Solver

* Improve the logic that determines constraint dispatch ordering
* Fix a crash in the type solver that arose when using multi-return
functions with `string.format`
* Fix https://github.com/luau-lang/luau/issues/1736
* Initial steps toward rethinking function generalization:
* Instead of generalizing every type in a function all at once, we will
instead generalize individual type variables once their bounds have been
fully resolved. This will make it possible to properly interleave type
function reduction and generalization.
* Magic functions are no longer considered magical in cases where they
are not explicitly called by the code.
* The most prominent example of this is in `for..in` loops where the
function call is part of the desugaring process.
* Almost all magic functions work by directly inspecting the AST, so
they can't work without an AST fragment anyway.
* Further, none of the magic functions we have are usefully used in this
way.

Co-authored-by: Andy Friesen <afriesen@roblox.com>
Co-authored-by: Ariel Weiss <aaronweiss@roblox.com>
Co-authored-by: Hunter Goldstein <hgoldstein@roblox.com>
Co-authored-by: Sora Kanosue <skanosue@roblox.com>
Co-authored-by: Talha Pathan <tpathan@roblox.com>
Co-authored-by: Varun Saini <vsaini@roblox.com>
Co-authored-by: Vighnesh Vijay <vvijay@roblox.com>
Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
2025-04-25 14:19:27 -07:00
142 changed files with 5393 additions and 1996 deletions

View file

@ -57,7 +57,7 @@ struct AutocompleteEntry
// Set if this suggestion matches the type expected in the context
TypeCorrectKind typeCorrect = TypeCorrectKind::None;
std::optional<const ClassType*> containingClass = std::nullopt;
std::optional<const ExternType*> containingExternType = std::nullopt;
std::optional<const Property*> prop = std::nullopt;
std::optional<std::string> documentationSymbol = std::nullopt;
Tags tags;
@ -85,7 +85,7 @@ struct AutocompleteResult
};
using StringCompletionCallback =
std::function<std::optional<AutocompleteEntryMap>(std::string tag, std::optional<const ClassType*> ctx, std::optional<std::string> contents)>;
std::function<std::optional<AutocompleteEntryMap>(std::string tag, std::optional<const ExternType*> ctx, std::optional<std::string> contents)>;
constexpr char kGeneratedAnonymousFunctionEntryName[] = "function (anonymous autofilled)";

View file

@ -3,6 +3,7 @@
#include "Luau/Ast.h"
#include "Luau/Constraint.h"
#include "Luau/ConstraintSet.h"
#include "Luau/ControlFlow.h"
#include "Luau/DataFlowGraph.h"
#include "Luau/EqSatSimplification.h"
@ -91,9 +92,8 @@ struct ConstraintGenerator
// Constraints that go straight to the solver.
std::vector<ConstraintPtr> constraints;
// Constraints that do not go to the solver right away. Other constraints
// will enqueue them during solving.
std::vector<ConstraintPtr> unqueuedConstraints;
// The set of all free types introduced during constraint generation.
DenseHashSet<TypeId> freeTypes{nullptr};
// Map a function's signature scope back to its signature type.
DenseHashMap<Scope*, TypeId> scopeToFunction{nullptr};
@ -151,6 +151,9 @@ struct ConstraintGenerator
std::vector<RequireCycle> requireCycles
);
ConstraintSet run(AstStatBlock* block);
ConstraintSet runOnFragment(const ScopePtr& resumeScope, AstStatBlock* block);
/**
* The entry point to the ConstraintGenerator. This will construct a set
* of scopes, constraints, and free types that can be solved later.
@ -269,7 +272,7 @@ private:
ControlFlow visit(const ScopePtr& scope, AstStatTypeAlias* alias);
ControlFlow visit(const ScopePtr& scope, AstStatTypeFunction* function);
ControlFlow visit(const ScopePtr& scope, AstStatDeclareGlobal* declareGlobal);
ControlFlow visit(const ScopePtr& scope, AstStatDeclareClass* declareClass);
ControlFlow visit(const ScopePtr& scope, AstStatDeclareExternType* declareExternType);
ControlFlow visit(const ScopePtr& scope, AstStatDeclareFunction* declareFunction);
ControlFlow visit(const ScopePtr& scope, AstStatError* error);
@ -481,9 +484,4 @@ private:
TypeId simplifyUnion(const ScopePtr& scope, Location location, TypeId left, TypeId right);
};
/** Borrow a vector of pointers from a vector of owning pointers to constraints.
*/
std::vector<NotNull<Constraint>> borrowConstraints(const std::vector<ConstraintPtr>& constraints);
} // namespace Luau

View file

@ -0,0 +1,32 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "Luau/Constraint.h"
#include "Luau/DenseHash.h"
#include "Luau/Error.h"
#include <vector>
namespace Luau
{
struct ConstraintSet
{
NotNull<Scope> rootScope;
std::vector<ConstraintPtr> constraints;
// The set of all free types created during constraint generation
DenseHashSet<TypeId> freeTypes{nullptr};
// Map a function's signature scope back to its signature type. Once we've
// dispatched all of the constraints pertaining to a particular free type,
// we use this mapping to generalize that free type.
DenseHashMap<Scope*, TypeId> scopeToFunction{nullptr};
// It is pretty uncommon for constraint generation to itself produce errors, but it can happen.
std::vector<TypeError> errors;
};
}

View file

@ -3,6 +3,7 @@
#pragma once
#include "Luau/Constraint.h"
#include "Luau/ConstraintSet.h"
#include "Luau/DataFlowGraph.h"
#include "Luau/DenseHash.h"
#include "Luau/EqSatSimplification.h"
@ -87,6 +88,7 @@ struct ConstraintSolver
NotNull<Simplifier> simplifier;
NotNull<TypeFunctionRuntime> typeFunctionRuntime;
// The entire set of constraints that the solver is trying to resolve.
ConstraintSet constraintSet;
std::vector<NotNull<Constraint>> constraints;
NotNull<DenseHashMap<Scope*, TypeId>> scopeToFunction;
NotNull<Scope> rootScope;
@ -140,6 +142,19 @@ struct ConstraintSolver
DenseHashMap<TypeId, const Constraint*> typeFunctionsToFinalize{nullptr};
explicit ConstraintSolver(
NotNull<Normalizer> normalizer,
NotNull<Simplifier> simplifier,
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
ModuleName moduleName,
NotNull<ModuleResolver> moduleResolver,
std::vector<RequireCycle> requireCycles,
DcrLogger* logger,
NotNull<const DataFlowGraph> dfg,
TypeCheckLimits limits,
ConstraintSet constraintSet
);
explicit ConstraintSolver(
NotNull<Normalizer> normalizer,
NotNull<Simplifier> simplifier,
@ -174,6 +189,9 @@ struct ConstraintSolver
bool isDone() const;
private:
/// A helper that does most of the setup work that is shared between the two constructors.
void initFreeTypeTracking();
void generalizeOneType(TypeId ty);
/**
@ -432,6 +450,10 @@ public:
void fillInDiscriminantTypes(NotNull<const Constraint> constraint, const std::vector<std::optional<TypeId>>& discriminantTypes);
};
/** Borrow a vector of pointers from a vector of owning pointers to constraints.
*/
std::vector<NotNull<Constraint>> borrowConstraints(const std::vector<ConstraintPtr>& constraints);
void dump(NotNull<Scope> rootScope, struct ToStringOptions& opts);
} // namespace Luau

View file

@ -173,7 +173,7 @@ private:
ControlFlow visit(AstStatTypeFunction* f);
ControlFlow visit(AstStatDeclareGlobal* d);
ControlFlow visit(AstStatDeclareFunction* d);
ControlFlow visit(AstStatDeclareClass* d);
ControlFlow visit(AstStatDeclareExternType* d);
ControlFlow visit(AstStatError* error);
DataFlowResult visitExpr(AstExpr* e);

View file

@ -332,11 +332,11 @@ struct TypePackMismatch
bool operator==(const TypePackMismatch& rhs) const;
};
struct DynamicPropertyLookupOnClassesUnsafe
struct DynamicPropertyLookupOnExternTypesUnsafe
{
TypeId ty;
bool operator==(const DynamicPropertyLookupOnClassesUnsafe& rhs) const;
bool operator==(const DynamicPropertyLookupOnExternTypesUnsafe& rhs) const;
};
struct UninhabitedTypeFunction
@ -499,7 +499,7 @@ using TypeErrorData = Variant<
TypesAreUnrelated,
NormalizationTooComplex,
TypePackMismatch,
DynamicPropertyLookupOnClassesUnsafe,
DynamicPropertyLookupOnExternTypesUnsafe,
UninhabitedTypeFunction,
UninhabitedTypePackFunction,
WhereClauseNeeded,

View file

@ -117,8 +117,7 @@ struct FileResolver
return std::nullopt;
}
// Make non-virtual when removing FFlagLuauImproveRequireByStringAutocomplete.
virtual std::optional<RequireSuggestions> getRequireSuggestions(const ModuleName& requirer, const std::optional<std::string>& pathString) const;
std::optional<RequireSuggestions> getRequireSuggestions(const ModuleName& requirer, const std::optional<std::string>& pathString) const;
std::shared_ptr<RequireSuggester> requireSuggester;
};

View file

@ -133,9 +133,9 @@ struct GenericTypeFinder : TypeOnceVisitor
return false;
}
bool visit(TypeId ty, const Luau::ClassType&) override
bool visit(TypeId ty, const Luau::ExternType&) override
{
// During function instantiation, classes are not traversed even if they have generics
// During function instantiation, extern types are not traversed even if they have generics
return false;
}
};

View file

@ -181,7 +181,7 @@ struct NormalizedStringType
bool isSubtype(const NormalizedStringType& subStr, const NormalizedStringType& superStr);
struct NormalizedClassType
struct NormalizedExternType
{
/** Has the following structure:
*
@ -192,7 +192,7 @@ struct NormalizedClassType
*
* Each TypeId is a class type.
*/
std::unordered_map<TypeId, TypeIds> classes;
std::unordered_map<TypeId, TypeIds> externTypes;
/**
* In order to maintain a consistent insertion order, we use this vector to
@ -245,7 +245,7 @@ enum class NormalizationResult
};
// A normalized type is either any, unknown, or one of the form P | T | F | G where
// * P is a union of primitive types (including singletons, classes and the error type)
// * P is a union of primitive types (including singletons, extern types and the error type)
// * T is a union of table types
// * F is a union of an intersection of function types
// * G is a union of generic/free/blocked types, intersected with a normalized type
@ -260,7 +260,7 @@ struct NormalizedType
// This type is either never, boolean type, or a boolean singleton.
TypeId booleans;
NormalizedClassType classes;
NormalizedExternType externTypes;
// The error part of the type.
// This type is either never or the error type.
@ -333,7 +333,7 @@ struct NormalizedType
// Helpers that improve readability of the above (they just say if the component is present)
bool hasTops() const;
bool hasBooleans() const;
bool hasClasses() const;
bool hasExternTypes() const;
bool hasErrors() const;
bool hasNils() const;
bool hasNumbers() const;
@ -391,10 +391,10 @@ public:
void unionTysWithTy(TypeIds& here, TypeId there);
TypeId unionOfTops(TypeId here, TypeId there);
TypeId unionOfBools(TypeId here, TypeId there);
void unionClassesWithClass(TypeIds& heres, TypeId there);
void unionClasses(TypeIds& heres, const TypeIds& theres);
void unionClassesWithClass(NormalizedClassType& heres, TypeId there);
void unionClasses(NormalizedClassType& heres, const NormalizedClassType& theres);
void unionExternTypesWithExternType(TypeIds& heres, TypeId there);
void unionExternTypes(TypeIds& heres, const TypeIds& theres);
void unionExternTypesWithExternType(NormalizedExternType& heres, TypeId there);
void unionExternTypes(NormalizedExternType& heres, const NormalizedExternType& theres);
void unionStrings(NormalizedStringType& here, const NormalizedStringType& there);
std::optional<TypePackId> unionOfTypePacks(TypePackId here, TypePackId there);
std::optional<TypeId> unionOfFunctions(TypeId here, TypeId there);
@ -423,8 +423,8 @@ public:
// ------- Normalizing intersections
TypeId intersectionOfTops(TypeId here, TypeId there);
TypeId intersectionOfBools(TypeId here, TypeId there);
void intersectClasses(NormalizedClassType& heres, const NormalizedClassType& theres);
void intersectClassesWithClass(NormalizedClassType& heres, TypeId there);
void intersectExternTypes(NormalizedExternType& heres, const NormalizedExternType& theres);
void intersectExternTypesWithExternType(NormalizedExternType& heres, TypeId there);
void intersectStrings(NormalizedStringType& here, const NormalizedStringType& there);
std::optional<TypePackId> intersectionOfTypePacks(TypePackId here, TypePackId there);
std::optional<TypeId> intersectionOfTables(TypeId here, TypeId there, SeenTablePropPairs& seenTablePropPairs, Set<TypeId>& seenSet);

View file

@ -16,7 +16,7 @@ struct Scope;
void quantify(TypeId ty, TypeLevel level);
// TODO: This is eerily similar to the pattern that NormalizedClassType
// TODO: This is eerily similar to the pattern that NormalizedExternType
// implements. We could, and perhaps should, merge them together.
template<typename K, typename V>
struct OrderedMap

View file

@ -22,7 +22,7 @@ struct InternalErrorReporter;
class TypeIds;
class Normalizer;
struct NormalizedClassType;
struct NormalizedExternType;
struct NormalizedFunctionType;
struct NormalizedStringType;
struct NormalizedType;
@ -121,7 +121,7 @@ struct SubtypingEnvironment
DenseHashMap<TypePackId, TypePackId> mappedGenericPacks{nullptr};
/*
* See the test cyclic_tables_are_assumed_to_be_compatible_with_classes for
* See the test cyclic_tables_are_assumed_to_be_compatible_with_extern_types for
* details.
*
* An empty value is equivalent to a nonexistent key.
@ -229,9 +229,8 @@ private:
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TableType* subTable, const TableType* superTable, NotNull<Scope> scope);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const MetatableType* subMt, const MetatableType* superMt, NotNull<Scope> scope);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const MetatableType* subMt, const TableType* superTable, NotNull<Scope> scope);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const ClassType* subClass, const ClassType* superClass, NotNull<Scope> scope);
SubtypingResult
isCovariantWith(SubtypingEnvironment& env, TypeId subTy, const ClassType* subClass, TypeId superTy, const TableType* superTable, NotNull<Scope>);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const ExternType* subExternType, const ExternType* superExternType, NotNull<Scope> scope);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, TypeId subTy, const ExternType* subExternType, TypeId superTy, const TableType* superTable, NotNull<Scope>);
SubtypingResult isCovariantWith(
SubtypingEnvironment& env,
const FunctionType* subFunction,
@ -259,11 +258,11 @@ private:
);
SubtypingResult isCovariantWith(
SubtypingEnvironment& env,
const NormalizedClassType& subClass,
const NormalizedClassType& superClass,
const NormalizedExternType& subExternType,
const NormalizedExternType& superExternType,
NotNull<Scope> scope
);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const NormalizedClassType& subClass, const TypeIds& superTables, NotNull<Scope> scope);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const NormalizedExternType& subExternType, const TypeIds& superTables, NotNull<Scope> scope);
SubtypingResult isCovariantWith(
SubtypingEnvironment& env,
const NormalizedStringType& subString,

View file

@ -44,6 +44,7 @@ struct ToStringOptions
bool hideTableKind = false; // If true, all tables will be surrounded with plain '{}'
bool hideNamedFunctionTypeParameters = false; // If true, type parameters of functions will be hidden at top-level.
bool hideFunctionSelfArgument = false; // If true, `self: X` will be omitted from the function signature if the function has self
bool hideTableAliasExpansions = false; // If true, all table aliases will not be expanded
bool useQuestionMarks = true; // If true, use a postfix ? for options, else write them out as unions that include nil.
size_t maxTableLength = size_t(FInt::LuauTableTypeMaximumStringifierLength); // Only applied to TableTypes
size_t maxTypeLength = size_t(FInt::LuauTypeMaximumStringifierLength);

View file

@ -74,10 +74,6 @@ struct FreeType
// This one got promoted to explicit
explicit FreeType(Scope* scope, TypeId lowerBound, TypeId upperBound, Polarity polarity = Polarity::Unknown);
explicit FreeType(Scope* scope, TypeLevel level, TypeId lowerBound, TypeId upperBound);
// Old constructors
explicit FreeType(TypeLevel level);
explicit FreeType(Scope* scope);
FreeType(Scope* scope, TypeLevel level);
int index;
TypeLevel level;
@ -291,7 +287,7 @@ struct MagicFunctionCallContext
{
NotNull<struct ConstraintSolver> solver;
NotNull<const Constraint> constraint;
const class AstExprCall* callSite;
NotNull<const AstExprCall> callSite;
TypePackId arguments;
TypePackId result;
};
@ -536,15 +532,15 @@ struct ClassUserData
virtual ~ClassUserData() {}
};
/** The type of a class.
/** The type of an external userdata exposed to Luau.
*
* Classes behave like tables in many ways, but there are some important differences:
* Extern types behave like tables in many ways, but there are some important differences:
*
* The properties of a class are always exactly known.
* Classes optionally have a parent class.
* Two different classes that share the same properties are nevertheless distinct and mutually incompatible.
* Extern types optionally have a parent type.
* Two different extern types that share the same properties are nevertheless distinct and mutually incompatible.
*/
struct ClassType
struct ExternType
{
using Props = TableType::Props;
@ -558,7 +554,7 @@ struct ClassType
std::optional<Location> definitionLocation;
std::optional<TableIndexer> indexer;
ClassType(
ExternType(
Name name,
Props props,
std::optional<TypeId> parent,
@ -579,7 +575,7 @@ struct ClassType
{
}
ClassType(
ExternType(
Name name,
Props props,
std::optional<TypeId> parent,
@ -779,7 +775,7 @@ using TypeVariant = Unifiable::Variant<
FunctionType,
TableType,
MetatableType,
ClassType,
ExternType,
AnyType,
UnionType,
IntersectionType,
@ -990,7 +986,7 @@ public:
const TypeId threadType;
const TypeId bufferType;
const TypeId functionType;
const TypeId classType;
const TypeId externType;
const TypeId tableType;
const TypeId emptyTableType;
const TypeId trueType;
@ -1002,6 +998,7 @@ public:
const TypeId noRefineType;
const TypeId falsyType;
const TypeId truthyType;
const TypeId notNilType;
const TypeId optionalNumberType;
const TypeId optionalStringType;
@ -1022,10 +1019,10 @@ TypeLevel* getMutableLevel(TypeId ty);
std::optional<TypeLevel> getLevel(TypePackId tp);
const Property* lookupClassProp(const ClassType* cls, const Name& name);
const Property* lookupExternTypeProp(const ExternType* cls, const Name& name);
// Whether `cls` is a subclass of `parent`
bool isSubclass(const ClassType* cls, const ClassType* parent);
bool isSubclass(const ExternType* cls, const ExternType* parent);
Type* asMutable(TypeId ty);

View file

@ -37,10 +37,6 @@ struct TypeArena
TypeId freshType(NotNull<BuiltinTypes> builtins, Scope* scope);
TypeId freshType(NotNull<BuiltinTypes> builtins, Scope* scope, TypeLevel level);
TypeId freshType_DEPRECATED(TypeLevel level);
TypeId freshType_DEPRECATED(Scope* scope);
TypeId freshType_DEPRECATED(Scope* scope, TypeLevel level);
TypePackId freshTypePack(Scope* scope, Polarity polarity = Polarity::Unknown);
TypePackId addTypePack(std::initializer_list<TypeId> types);

View file

@ -11,7 +11,7 @@ namespace Luau
struct TypeRehydrationOptions
{
std::unordered_set<std::string> bannedNames;
bool expandClassProps = false;
bool expandExternTypeProps = false;
};
void attachTypeData(SourceModule& source, Module& result);

View file

@ -160,7 +160,7 @@ private:
void visit(AstTypeList types);
void visit(AstStatDeclareFunction* stat);
void visit(AstStatDeclareGlobal* stat);
void visit(AstStatDeclareClass* stat);
void visit(AstStatDeclareExternType* stat);
void visit(AstStatError* stat);
void visit(AstExpr* expr, ValueContext context);
void visit(AstExprGroup* expr, ValueContext context);

View file

@ -205,7 +205,7 @@ struct TypeFunctionTableType
std::optional<TypeFunctionTypeId> metatable;
};
struct TypeFunctionClassType
struct TypeFunctionExternType
{
using Name = std::string;
using Props = std::map<Name, TypeFunctionProperty>;
@ -222,7 +222,7 @@ struct TypeFunctionClassType
std::optional<TypeFunctionTypeId> readParent;
std::optional<TypeFunctionTypeId> writeParent;
TypeId classTy;
TypeId externTy;
};
struct TypeFunctionGenericType
@ -244,7 +244,7 @@ using TypeFunctionTypeVariant = Luau::Variant<
TypeFunctionNegationType,
TypeFunctionFunctionType,
TypeFunctionTableType,
TypeFunctionClassType,
TypeFunctionExternType,
TypeFunctionGenericType>;
struct TypeFunctionType

View file

@ -29,7 +29,7 @@ struct SingletonType;
struct FunctionType;
struct TableType;
struct MetatableType;
struct ClassType;
struct ExternType;
struct AnyType;
struct UnionType;
struct IntersectionType;

View file

@ -90,11 +90,11 @@ struct TypeChecker
ControlFlow check(const ScopePtr& scope, TypeId ty, const ScopePtr& funScope, const AstStatLocalFunction& function);
ControlFlow check(const ScopePtr& scope, const AstStatTypeAlias& typealias);
ControlFlow check(const ScopePtr& scope, const AstStatTypeFunction& typefunction);
ControlFlow check(const ScopePtr& scope, const AstStatDeclareClass& declaredClass);
ControlFlow check(const ScopePtr& scope, const AstStatDeclareExternType& declaredExternType);
ControlFlow check(const ScopePtr& scope, const AstStatDeclareFunction& declaredFunction);
void prototype(const ScopePtr& scope, const AstStatTypeAlias& typealias, int subLevel = 0);
void prototype(const ScopePtr& scope, const AstStatDeclareClass& declaredClass);
void prototype(const ScopePtr& scope, const AstStatDeclareExternType& declaredExternType);
ControlFlow checkBlock(const ScopePtr& scope, const AstStatBlock& statement);
ControlFlow checkBlockWithoutRecursionCheck(const ScopePtr& scope, const AstStatBlock& statement);
@ -487,7 +487,7 @@ private:
/**
* A set of incorrect class definitions which is used to avoid a second-pass analysis.
*/
DenseHashSet<const AstStatDeclareClass*> incorrectClassDefinitions{nullptr};
DenseHashSet<const AstStatDeclareExternType*> incorrectExternTypeDefinitions{nullptr};
std::vector<std::pair<TypeId, ScopePtr>> deferredQuantification;
};

View file

@ -185,6 +185,8 @@ TypePackIterator begin(TypePackId tp);
TypePackIterator begin(TypePackId tp, const TxnLog* log);
TypePackIterator end(TypePackId tp);
TypePackId getTail(TypePackId tp);
using SeenSet = std::set<std::pair<const void*, const void*>>;
bool areEqual(SeenSet& seen, const TypePackVar& lhs, const TypePackVar& rhs);

View file

@ -140,7 +140,7 @@ private:
void tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection = false, const LiteralProperties* aliasableMap = nullptr);
void tryUnifyScalarShape(TypeId subTy, TypeId superTy, bool reversed);
void tryUnifyWithMetatable(TypeId subTy, TypeId superTy, bool reversed);
void tryUnifyWithClass(TypeId subTy, TypeId superTy, bool reversed);
void tryUnifyWithExternType(TypeId subTy, TypeId superTy, bool reversed);
void tryUnifyNegations(TypeId subTy, TypeId superTy);
TypePackId tryApplyOverloadedFunction(TypeId function, const NormalizedFunctionType& overloads, TypePackId args);

View file

@ -126,7 +126,7 @@ struct GenericTypeVisitor
{
return visit(ty);
}
virtual bool visit(TypeId ty, const ClassType& ctv)
virtual bool visit(TypeId ty, const ExternType& etv)
{
return visit(ty);
}
@ -313,11 +313,11 @@ struct GenericTypeVisitor
traverse(mtv->metatable);
}
}
else if (auto ctv = get<ClassType>(ty))
else if (auto etv = get<ExternType>(ty))
{
if (visit(ty, *ctv))
if (visit(ty, *etv))
{
for (const auto& [name, prop] : ctv->props)
for (const auto& [name, prop] : etv->props)
{
if (FFlag::LuauSolverV2)
{
@ -335,16 +335,16 @@ struct GenericTypeVisitor
traverse(prop.type());
}
if (ctv->parent)
traverse(*ctv->parent);
if (etv->parent)
traverse(*etv->parent);
if (ctv->metatable)
traverse(*ctv->metatable);
if (etv->metatable)
traverse(*etv->metatable);
if (ctv->indexer)
if (etv->indexer)
{
traverse(ctv->indexer->indexType);
traverse(ctv->indexer->indexResultType);
traverse(etv->indexer->indexType);
traverse(etv->indexer->indexResultType);
}
}
}
@ -396,7 +396,7 @@ struct GenericTypeVisitor
traverse(unwrapped);
// Visiting into LazyType that hasn't been unwrapped may necessarily cause infinite expansion, so we don't do that on purpose.
// Asserting also makes no sense, because the type _will_ happen here, most likely as a property of some ClassType
// Asserting also makes no sense, because the type _will_ happen here, most likely as a property of some ExternType
// that doesn't need to be expanded.
}
else if (auto stv = get<SingletonType>(ty))

View file

@ -88,7 +88,7 @@ TypePackId Anyification::clean(TypePackId tp)
bool Anyification::ignoreChildren(TypeId ty)
{
if (get<ClassType>(ty))
if (get<ExternType>(ty))
return true;
return ty->persistent;

View file

@ -31,7 +31,7 @@ bool ApplyTypeFunction::ignoreChildren(TypeId ty)
{
if (get<GenericType>(ty))
return true;
else if (get<ClassType>(ty))
else if (get<ExternType>(ty))
return true;
else
return false;

View file

@ -8,6 +8,8 @@
#include <math.h>
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
namespace Luau
{
@ -431,8 +433,16 @@ struct AstJsonEncoder : public AstVisitor
if (node->self)
PROP(self);
PROP(args);
if (node->returnAnnotation)
PROP(returnAnnotation);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
if (node->returnAnnotation)
PROP(returnAnnotation);
}
else
{
if (node->returnAnnotation_DEPRECATED)
write("returnAnnotation", node->returnAnnotation_DEPRECATED);
}
PROP(vararg);
PROP(varargLocation);
if (node->varargAnnotation)
@ -465,26 +475,26 @@ struct AstJsonEncoder : public AstVisitor
writeRaw("}");
}
void write(const AstGenericType& genericType)
void write(class AstGenericType* genericType)
{
writeRaw("{");
bool c = pushComma();
writeType("AstGenericType");
write("name", genericType.name);
if (genericType.defaultValue)
write("luauType", genericType.defaultValue);
write("name", genericType->name);
if (genericType->defaultValue)
write("luauType", genericType->defaultValue);
popComma(c);
writeRaw("}");
}
void write(const AstGenericTypePack& genericTypePack)
void write(class AstGenericTypePack* genericTypePack)
{
writeRaw("{");
bool c = pushComma();
writeType("AstGenericTypePack");
write("name", genericTypePack.name);
if (genericTypePack.defaultValue)
write("luauType", genericTypePack.defaultValue);
write("name", genericTypePack->name);
if (genericTypePack->defaultValue)
write("luauType", genericTypePack->defaultValue);
popComma(c);
writeRaw("}");
}
@ -902,7 +912,10 @@ struct AstJsonEncoder : public AstVisitor
PROP(paramNames);
PROP(vararg);
PROP(varargLocation);
PROP(retTypes);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
PROP(retTypes);
else
write("retTypes", node->retTypes_DEPRECATED);
PROP(generics);
PROP(genericPacks);
}
@ -923,7 +936,7 @@ struct AstJsonEncoder : public AstVisitor
);
}
void write(const AstDeclaredClassProp& prop)
void write(const AstDeclaredExternTypeProperty& prop)
{
writeRaw("{");
bool c = pushComma();
@ -936,7 +949,7 @@ struct AstJsonEncoder : public AstVisitor
writeRaw("}");
}
void write(class AstStatDeclareClass* node)
void write(class AstStatDeclareExternType* node)
{
writeNode(
node,
@ -1048,7 +1061,10 @@ struct AstJsonEncoder : public AstVisitor
PROP(genericPacks);
PROP(argTypes);
PROP(argNames);
PROP(returnTypes);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
PROP(returnTypes);
else
write("returnTypes", node->returnTypes_DEPRECATED);
}
);
}
@ -1429,7 +1445,7 @@ struct AstJsonEncoder : public AstVisitor
return false;
}
bool visit(class AstStatDeclareClass* node) override
bool visit(class AstStatDeclareExternType* node) override
{
write(node);
return false;

View file

@ -574,11 +574,11 @@ std::optional<DocumentationSymbol> getDocumentationSymbolAtPosition(const Source
return checkOverloadedDocumentationSymbol(module, propIt->second.type(), parentExpr, propIt->second.documentationSymbol);
}
}
else if (const ClassType* ctv = get<ClassType>(parentTy))
else if (const ExternType* etv = get<ExternType>(parentTy))
{
while (ctv)
while (etv)
{
if (auto propIt = ctv->props.find(indexName->index.value); propIt != ctv->props.end())
if (auto propIt = etv->props.find(indexName->index.value); propIt != etv->props.end())
{
if (FFlag::LuauSolverV2)
{
@ -590,7 +590,7 @@ std::optional<DocumentationSymbol> getDocumentationSymbolAtPosition(const Source
module, propIt->second.type(), parentExpr, propIt->second.documentationSymbol
);
}
ctv = ctv->parent ? Luau::get<Luau::ClassType>(*ctv->parent) : nullptr;
etv = etv->parent ? Luau::get<Luau::ExternType>(*etv->parent) : nullptr;
}
}
else if (const PrimitiveType* ptv = get<PrimitiveType>(parentTy); ptv && ptv->metatable)

View file

@ -24,14 +24,10 @@ LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTINT(LuauTypeInferIterationLimit)
LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAGVARIABLE(DebugLuauMagicVariableNames)
LUAU_FASTFLAG(LuauExposeRequireByStringAutocomplete)
LUAU_FASTFLAGVARIABLE(LuauAutocompleteRefactorsForIncrementalAutocomplete)
LUAU_FASTFLAGVARIABLE(LuauAutocompleteUsesModuleForTypeCompatibility)
LUAU_FASTFLAGVARIABLE(LuauAutocompleteUnionCopyPreviousSeen)
LUAU_FASTFLAGVARIABLE(LuauAutocompleteMissingFollows)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
static const std::unordered_set<std::string> kStatementStartingKeywords =
{"while", "if", "local", "repeat", "function", "do", "for", "return", "break", "continue", "type", "export"};
@ -311,7 +307,7 @@ static void autocompleteProps(
const std::vector<AstNode*>& nodes,
AutocompleteEntryMap& result,
std::unordered_set<TypeId>& seen,
std::optional<const ClassType*> containingClass = std::nullopt
std::optional<const ExternType*> containingExternType = std::nullopt
)
{
rootTy = follow(rootTy);
@ -334,8 +330,8 @@ static void autocompleteProps(
if (calledWithSelf == ftv->hasSelf)
return true;
// Calls on classes require strict match between how function is declared and how it's called
if (get<ClassType>(rootTy))
// Calls on extern types require strict match between how function is declared and how it's called
if (get<ExternType>(rootTy))
return false;
// When called with ':', but declared without 'self', it is invalid if a function has incompatible first argument or no arguments at all
@ -368,7 +364,7 @@ static void autocompleteProps(
return calledWithSelf;
};
auto fillProps = [&](const ClassType::Props& props)
auto fillProps = [&](const ExternType::Props& props)
{
for (const auto& [name, prop] : props)
{
@ -401,7 +397,7 @@ static void autocompleteProps(
prop.deprecated,
isWrongIndexer(type),
typeCorrect,
containingClass,
containingExternType,
&prop,
prop.documentationSymbol,
{},
@ -432,12 +428,12 @@ static void autocompleteProps(
}
};
if (auto cls = get<ClassType>(ty))
if (auto cls = get<ExternType>(ty))
{
containingClass = containingClass.value_or(cls);
containingExternType = containingExternType.value_or(cls);
fillProps(cls->props);
if (cls->parent)
autocompleteProps(module, typeArena, builtinTypes, rootTy, *cls->parent, indexType, nodes, result, seen, containingClass);
autocompleteProps(module, typeArena, builtinTypes, rootTy, *cls->parent, indexType, nodes, result, seen, containingExternType);
}
else if (auto tbl = get<TableType>(ty))
fillProps(tbl->props);
@ -491,7 +487,7 @@ static void autocompleteProps(
// If we don't do this, and we have the misfortune of receiving a
// recursive union like:
//
// t1 where t1 = t1 | Class
// t1 where t1 = t1 | ExternType
//
// Then we are on a one way journey to a stack overflow.
if (FFlag::LuauAutocompleteUnionCopyPreviousSeen)
@ -591,7 +587,7 @@ AutocompleteEntryMap autocompleteProps(
AutocompleteEntryMap autocompleteModuleTypes(const Module& module, const ScopePtr& scopeAtPosition, Position position, std::string_view moduleName)
{
AutocompleteEntryMap result;
ScopePtr startScope = FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete ? scopeAtPosition : findScopeAtPosition(module, position);
ScopePtr startScope = scopeAtPosition;
for (ScopePtr& scope = startScope; scope; scope = scope->parent)
{
if (auto it = scope->importedTypeBindings.find(std::string(moduleName)); it != scope->importedTypeBindings.end())
@ -703,6 +699,30 @@ static std::optional<TypeId> findTypeElementAt(const AstTypeList& astTypeList, T
return {};
}
static std::optional<TypeId> findTypeElementAt(AstTypePack* astTypePack, TypePackId tp, Position position)
{
LUAU_ASSERT(FFlag::LuauStoreReturnTypesAsPackOnAst);
if (const auto typePack = astTypePack->as<AstTypePackExplicit>())
{
return findTypeElementAt(typePack->typeList, tp, position);
}
else if (const auto variadic = astTypePack->as<AstTypePackVariadic>())
{
if (variadic->location.containsClosed(position))
{
auto [_, tail] = flatten(tp);
if (tail)
{
if (const VariadicTypePack* vtp = get<VariadicTypePack>(follow(*tail)))
return findTypeElementAt(variadic->variadicType, vtp->ty, position);
}
}
}
return {};
}
static std::optional<TypeId> findTypeElementAt(AstType* astType, TypeId ty, Position position)
{
ty = follow(ty);
@ -723,8 +743,16 @@ static std::optional<TypeId> findTypeElementAt(AstType* astType, TypeId ty, Posi
if (auto element = findTypeElementAt(type->argTypes, ftv->argTypes, position))
return element;
if (auto element = findTypeElementAt(type->returnTypes, ftv->retTypes, position))
return element;
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
if (auto element = findTypeElementAt(type->returnTypes, ftv->retTypes, position))
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
@ -733,7 +761,7 @@ static std::optional<TypeId> findTypeElementAt(AstType* astType, TypeId ty, Posi
std::optional<TypeId> getLocalTypeInScopeAt(const Module& module, const ScopePtr& scopeAtPosition, Position position, AstLocal* local)
{
if (ScopePtr scope = FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete ? scopeAtPosition : findScopeAtPosition(module, position))
if (ScopePtr scope = scopeAtPosition)
{
for (const auto& [name, binding] : scope->bindings)
{
@ -875,7 +903,7 @@ AutocompleteEntryMap autocompleteTypeNames(
{
AutocompleteEntryMap result;
ScopePtr startScope = FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete ? scopeAtPosition : findScopeAtPosition(module, position);
ScopePtr startScope = scopeAtPosition;
for (ScopePtr scope = startScope; scope; scope = scope->parent)
{
@ -1054,29 +1082,46 @@ AutocompleteEntryMap autocompleteTypeNames(
}
}
if (!node->returnAnnotation)
return result;
for (size_t i = 0; i < node->returnAnnotation->types.size; i++)
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
AstType* ret = node->returnAnnotation->types.data[i];
if (!node->returnAnnotation)
return result;
if (ret->location.containsClosed(position))
if (const auto typePack = node->returnAnnotation->as<AstTypePackExplicit>())
{
if (const FunctionType* ftv = tryGetExpectedFunctionType(module, node))
for (size_t i = 0; i < typePack->typeList.types.size; i++)
{
if (auto ty = tryGetTypePackTypeAt(ftv->retTypes, i))
tryAddTypeCorrectSuggestion(result, startScope, topType, *ty, position);
AstType* ret = typePack->typeList.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;
}
}
// TODO: with additional type information, we could suggest inferred return type here
break;
if (AstTypePack* retTp = typePack->typeList.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, startScope, topType, *ty, position);
}
}
}
}
}
}
if (AstTypePack* retTp = node->returnAnnotation->tailType)
{
if (auto variadic = retTp->as<AstTypePackVariadic>())
else if (auto variadic = node->returnAnnotation->as<AstTypePackVariadic>())
{
if (variadic->location.containsClosed(position))
{
@ -1088,6 +1133,43 @@ 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, startScope, topType, *ty, position);
}
}
}
}
}
}
return result;
@ -1208,7 +1290,7 @@ static AutocompleteEntryMap autocompleteStatement(
)
{
// This is inefficient. :(
ScopePtr scope = FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete ? scopeAtPosition : findScopeAtPosition(module, position);
ScopePtr scope = scopeAtPosition;
AutocompleteEntryMap result;
@ -1386,7 +1468,7 @@ static AutocompleteContext autocompleteExpression(
else
{
// This is inefficient. :(
ScopePtr scope = FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete ? scopeAtPosition : findScopeAtPosition(module, position);
ScopePtr scope = scopeAtPosition;
while (scope)
{
@ -1455,7 +1537,7 @@ static AutocompleteResult autocompleteExpression(
return {result, ancestry, context};
}
static std::optional<const ClassType*> getMethodContainingClass(const ModulePtr& module, AstExpr* funcExpr)
static std::optional<const ExternType*> getMethodContainingExternType(const ModulePtr& module, AstExpr* funcExpr)
{
AstExpr* parentExpr = nullptr;
if (auto indexName = funcExpr->as<AstExprIndexName>())
@ -1479,14 +1561,14 @@ static std::optional<const ClassType*> getMethodContainingClass(const ModulePtr&
Luau::TypeId parentType = Luau::follow(*parentIt);
if (auto parentClass = Luau::get<ClassType>(parentType))
if (auto parentExternType = Luau::get<ExternType>(parentType))
{
return parentClass;
return parentExternType;
}
if (auto parentUnion = Luau::get<UnionType>(parentType))
{
return returnFirstNonnullOptionOfType<ClassType>(parentUnion);
return returnFirstNonnullOptionOfType<ExternType>(parentUnion);
}
return std::nullopt;
@ -1544,10 +1626,7 @@ static std::optional<AutocompleteEntryMap> convertRequireSuggestionsToAutocomple
{
AutocompleteEntry entry = {AutocompleteEntryKind::RequirePath};
entry.insertText = std::move(suggestion.fullPath);
if (FFlag::LuauExposeRequireByStringAutocomplete)
{
entry.tags = std::move(suggestion.tags);
}
entry.tags = std::move(suggestion.tags);
result[std::move(suggestion.label)] = std::move(entry);
}
return result;
@ -1608,7 +1687,7 @@ static std::optional<AutocompleteEntryMap> autocompleteStringParams(
{
return convertRequireSuggestionsToAutocompleteEntryMap(fileResolver->getRequireSuggestions(module->name, candidateString));
}
if (std::optional<AutocompleteEntryMap> ret = callback(tag, getMethodContainingClass(module, candidate->func), candidateString))
if (std::optional<AutocompleteEntryMap> ret = callback(tag, getMethodContainingExternType(module, candidate->func), candidateString))
{
return ret;
}
@ -1776,7 +1855,7 @@ static std::optional<AutocompleteEntry> makeAnonymousAutofilled(
if (!type)
return std::nullopt;
const ScopePtr scope = FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete ? scopeAtPosition : findScopeAtPosition(*module, position);
const ScopePtr scope = scopeAtPosition;
if (!scope)
return std::nullopt;

View file

@ -32,10 +32,9 @@
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauNonReentrantGeneralization2)
LUAU_FASTFLAGVARIABLE(LuauTableCloneClonesType3)
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope)
LUAU_FASTFLAGVARIABLE(LuauFollowTableFreeze)
LUAU_FASTFLAGVARIABLE(LuauUserTypeFunTypecheck)
LUAU_FASTFLAGVARIABLE(LuauMagicFreezeCheckBlocked)
LUAU_FASTFLAGVARIABLE(LuauFormatUseLastPosition)
namespace Luau
{
@ -344,7 +343,7 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
if (auto it = globals.globalScope->exportedTypeBindings.find("vector"); it != globals.globalScope->exportedTypeBindings.end())
{
TypeId vectorTy = it->second.type;
ClassType* vectorCls = getMutable<ClassType>(vectorTy);
ExternType* vectorCls = getMutable<ExternType>(vectorTy);
vectorCls->metatable = arena.addType(TableType{{}, std::nullopt, TypeLevel{}, TableState::Sealed});
TableType* metatableTy = Luau::getMutable<TableType>(vectorCls->metatable);
@ -705,6 +704,14 @@ bool MagicFormat::typeCheck(const MagicFunctionTypeCheckContext& context)
return true;
}
// CLI-150726: The block below effectively constructs a type pack and then type checks it by going parameter-by-parameter.
// This does _not_ handle cases like:
//
// local foo : () -> (...string) = (nil :: any)
// print(string.format("%s %d %s", foo()))
//
// ... which should be disallowed.
std::vector<TypeId> expected = parseFormatString(context.builtinTypes, fmt->value.data, fmt->value.size);
const auto& [params, tail] = flatten(context.arguments);
@ -716,7 +723,9 @@ bool MagicFormat::typeCheck(const MagicFunctionTypeCheckContext& context)
{
TypeId actualTy = params[i + paramOffset];
TypeId expectedTy = expected[i];
Location location = context.callSite->args.data[i + (calledWithSelf ? 0 : paramOffset)]->location;
Location location = FFlag::LuauFormatUseLastPosition
? context.callSite->args.data[std::min(context.callSite->args.size - 1, i + (calledWithSelf ? 0 : paramOffset))]->location
: context.callSite->args.data[i + (calledWithSelf ? 0 : paramOffset)]->location;
// use subtyping instead here
SubtypingResult result = context.typechecker->subtyping->isSubtype(actualTy, expectedTy, context.checkScope);
@ -1529,8 +1538,7 @@ bool MagicClone::infer(const MagicFunctionCallContext& context)
tableType->scope = context.constraint->scope.get();
}
if (FFlag::LuauTrackInteriorFreeTypesOnScope)
trackInteriorFreeType(context.constraint->scope.get(), resultType);
trackInteriorFreeType(context.constraint->scope.get(), resultType);
TypePackId clonedTypePack = arena->addTypePack({resultType});
asMutable(context.result)->ty.emplace<BoundTypePack>(clonedTypePack);
@ -1541,8 +1549,7 @@ bool MagicClone::infer(const MagicFunctionCallContext& context)
static std::optional<TypeId> freezeTable(TypeId inputType, const MagicFunctionCallContext& context)
{
TypeArena* arena = context.solver->arena;
if (FFlag::LuauFollowTableFreeze)
inputType = follow(inputType);
inputType = follow(inputType);
if (auto mt = get<MetatableType>(inputType))
{
std::optional<TypeId> frozenTable = freezeTable(mt->table, context);

View file

@ -355,7 +355,7 @@ private:
t->metatable = shallowClone(t->metatable);
}
void cloneChildren(ClassType* t)
void cloneChildren(ExternType* t)
{
for (auto& [_, p] : t->props)
p = shallowClone(p);

View file

@ -20,7 +20,7 @@ struct ReferenceCountInitializer : TypeOnceVisitor
DenseHashSet<TypeId>* result;
ReferenceCountInitializer(DenseHashSet<TypeId>* result)
explicit ReferenceCountInitializer(DenseHashSet<TypeId>* result)
: result(result)
{
}
@ -43,24 +43,15 @@ struct ReferenceCountInitializer : TypeOnceVisitor
return false;
}
bool visit(TypeId ty, const ClassType&) override
bool visit(TypeId ty, const ExternType&) override
{
// ClassTypes never contain free types.
// ExternTypes never contain free types.
return false;
}
bool visit(TypeId, const TypeFunctionInstanceType&) override
{
// We do not consider reference counted types that are inside a type
// function to be part of the reachable reference counted types.
// Otherwise, code can be constructed in just the right way such
// that two type functions both claim to mutate a free type, which
// prevents either type function from trying to generalize it, so
// we potentially get stuck.
//
// The default behavior here is `true` for "visit the child types"
// of this type, hence:
return false;
return FFlag::DebugLuauGreedyGeneralization;
}
};
@ -130,8 +121,10 @@ DenseHashSet<TypeId> Constraint::getMaybeMutatedFreeTypes() const
}
else if (auto hic = get<HasIndexerConstraint>(*this))
{
if (FFlag::DebugLuauGreedyGeneralization)
rci.traverse(hic->subjectType);
rci.traverse(hic->resultType);
// `HasIndexerConstraint` should not mutate `subjectType` or `indexType`.
// `HasIndexerConstraint` should not mutate `indexType`.
}
else if (auto apc = get<AssignPropConstraint>(*this))
{
@ -150,6 +143,10 @@ DenseHashSet<TypeId> Constraint::getMaybeMutatedFreeTypes() const
rci.traverse(ty);
// `UnpackConstraint` should not mutate `sourcePack`.
}
else if (auto rpc = get<ReduceConstraint>(*this); FFlag::DebugLuauGreedyGeneralization && rpc)
{
rci.traverse(rpc->ty);
}
else if (auto rpc = get<ReducePackConstraint>(*this))
{
rci.traverse(rpc->tp);

View file

@ -34,15 +34,11 @@ LUAU_FASTINT(LuauCheckRecursionLimit)
LUAU_FASTFLAG(DebugLuauLogSolverToJson)
LUAU_FASTFLAG(DebugLuauMagicTypes)
LUAU_FASTFLAG(LuauNonReentrantGeneralization2)
LUAU_FASTFLAG(LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
LUAU_FASTFLAGVARIABLE(LuauPropagateExpectedTypesForCalls)
LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
LUAU_FASTFLAGVARIABLE(LuauTrackInteriorFreeTypesOnScope)
LUAU_FASTFLAGVARIABLE(LuauUngeneralizedTypesForRecursiveFunctions)
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
LUAU_FASTFLAGVARIABLE(LuauDoNotLeakNilInRefinement)
LUAU_FASTFLAG(LuauUserTypeFunTypecheck)
LUAU_FASTFLAGVARIABLE(LuauRetainDefinitionAliasLocations)
@ -50,9 +46,12 @@ LUAU_FASTFLAG(LuauDeprecatedAttribute)
LUAU_FASTFLAGVARIABLE(LuauCacheInferencePerAstExpr)
LUAU_FASTFLAGVARIABLE(LuauAlwaysResolveAstTypes)
LUAU_FASTFLAGVARIABLE(LuauWeakNilRefinementType)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
LUAU_FASTFLAG(LuauGlobalVariableModuleIsolation)
LUAU_FASTFLAGVARIABLE(LuauNoTypeFunctionsNamedTypeOf)
LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions)
LUAU_FASTFLAG(LuauDoNotAddUpvalueTypesToLocalType)
namespace Luau
{
@ -157,7 +156,7 @@ struct HasFreeType : TypeOnceVisitor
return true;
}
bool visit(TypeId ty, const ClassType&) override
bool visit(TypeId ty, const ExternType&) override
{
return false;
}
@ -218,6 +217,32 @@ ConstraintGenerator::ConstraintGenerator(
LUAU_ASSERT(module);
}
ConstraintSet ConstraintGenerator::run(AstStatBlock* block)
{
visitModuleRoot(block);
return ConstraintSet{
NotNull{rootScope},
std::move(constraints),
std::move(freeTypes),
std::move(scopeToFunction),
std::move(errors)
};
}
ConstraintSet ConstraintGenerator::runOnFragment(const ScopePtr& resumeScope, AstStatBlock* block)
{
visitFragmentRoot(resumeScope, block);
return ConstraintSet{
NotNull{rootScope},
std::move(constraints),
std::move(freeTypes),
std::move(scopeToFunction),
std::move(errors)
};
}
void ConstraintGenerator::visitModuleRoot(AstStatBlock* block)
{
LUAU_TIMETRACE_SCOPE("ConstraintGenerator::visitModuleRoot", "Typechecking");
@ -263,8 +288,7 @@ void ConstraintGenerator::visitModuleRoot(AstStatBlock* block)
GeneralizationConstraint{
result,
moduleFnTy,
(FFlag::LuauNonReentrantGeneralization2 || FFlag::LuauTrackInteriorFreeTypesOnScope) ? std::vector<TypeId>{}
: std::move(DEPRECATED_interiorTypes.back())
std::vector<TypeId>{},
}
);
@ -273,7 +297,7 @@ void ConstraintGenerator::visitModuleRoot(AstStatBlock* block)
scope->interiorFreeTypes = std::move(interiorFreeTypes.back().types);
scope->interiorFreeTypePacks = std::move(interiorFreeTypes.back().typePacks);
}
else if (FFlag::LuauTrackInteriorFreeTypesOnScope)
else
scope->interiorFreeTypes = std::move(DEPRECATED_interiorTypes.back());
getMutable<BlockedType>(result)->setOwner(genConstraint);
@ -359,17 +383,17 @@ TypeId ConstraintGenerator::freshType(const ScopePtr& scope, Polarity polarity)
{
auto ft = Luau::freshType(arena, builtinTypes, scope.get(), polarity);
interiorFreeTypes.back().types.push_back(ft);
return ft;
}
else if (FFlag::LuauTrackInteriorFreeTypesOnScope)
{
auto ft = Luau::freshType(arena, builtinTypes, scope.get());
DEPRECATED_interiorTypes.back().push_back(ft);
if (FFlag::DebugLuauGreedyGeneralization)
freeTypes.insert(ft);
return ft;
}
else
{
return Luau::freshType(arena, builtinTypes, scope.get());
auto ft = Luau::freshType(arena, builtinTypes, scope.get());
DEPRECATED_interiorTypes.back().push_back(ft);
return ft;
}
}
@ -578,15 +602,8 @@ void ConstraintGenerator::computeRefinement(
// When the top-level expression is `t[x]`, we want to refine it into `nil`, not `never`.
LUAU_ASSERT(refis->get(proposition->key->def));
if (FFlag::LuauDoNotLeakNilInRefinement)
{
refis->get(proposition->key->def)->shouldAppendNilType =
(sense || !eq) && containsSubscriptedDefinition(proposition->key->def) && !proposition->implicitFromCall;
}
else
{
refis->get(proposition->key->def)->shouldAppendNilType = (sense || !eq) && containsSubscriptedDefinition(proposition->key->def);
}
refis->get(proposition->key->def)->shouldAppendNilType =
(sense || !eq) && containsSubscriptedDefinition(proposition->key->def) && !proposition->implicitFromCall;
}
}
@ -633,7 +650,7 @@ struct FindSimplificationBlockers : TypeOnceVisitor
return false;
}
bool visit(TypeId, const ClassType&) override
bool visit(TypeId, const ExternType&) override
{
return false;
}
@ -858,7 +875,7 @@ void ConstraintGenerator::checkAliases(const ScopePtr& scope, AstStatBlock* bloc
aliasDefinitionLocations[function->name.value] = function->location;
}
else if (auto classDeclaration = stat->as<AstStatDeclareClass>())
else if (auto classDeclaration = stat->as<AstStatDeclareExternType>())
{
if (scope->exportedTypeBindings.count(classDeclaration->name.value))
{
@ -1052,7 +1069,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStat* stat)
return visit(scope, s);
else if (auto s = stat->as<AstStatDeclareFunction>())
return visit(scope, s);
else if (auto s = stat->as<AstStatDeclareClass>())
else if (auto s = stat->as<AstStatDeclareExternType>())
return visit(scope, s);
else if (auto s = stat->as<AstStatError>())
return visit(scope, s);
@ -1273,11 +1290,8 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatForIn* forI
for (AstLocal* var : forIn->vars)
{
TypeId assignee = arena->addType(BlockedType{});
variableTypes.push_back(assignee);
TypeId loopVar = arena->addType(BlockedType{});
localTypes[loopVar].insert(assignee);
variableTypes.push_back(loopVar);
if (var->annotation)
{
@ -1296,6 +1310,23 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatForIn* forI
loopScope, getLocation(forIn->values), IterableConstraint{iterator, variableTypes, forIn->values.data[0], &module->astForInNextTypes}
);
if (FFlag::LuauAddCallConstraintForIterableFunctions)
{
// Add an intersection ReduceConstraint for the key variable to denote that it can't be nil
AstLocal* keyVar = *forIn->vars.begin();
const DefId keyDef = dfg->getDef(keyVar);
const TypeId loopVar = loopScope->lvalueTypes[keyDef];
const TypeId intersectionTy =
createTypeFunctionInstance(builtinTypeFunctions().intersectFunc, {loopVar, builtinTypes->notNilType}, {}, loopScope, keyVar->location);
loopScope->bindings[keyVar] = Binding{intersectionTy, keyVar->location};
loopScope->lvalueTypes[keyDef] = intersectionTy;
auto c = addConstraint(loopScope, keyVar->location, ReduceConstraint{intersectionTy});
c->dependencies.push_back(iterable);
}
for (TypeId var : variableTypes)
{
auto bt = getMutable<BlockedType>(var);
@ -1378,7 +1409,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatLocalFuncti
FunctionSignature sig = checkFunctionSignature(scope, function->func, /* expectedType */ std::nullopt, function->name->location);
sig.bodyScope->bindings[function->name] = Binding{sig.signature, function->name->location};
bool sigFullyDefined = !hasFreeType(sig.signature);
bool sigFullyDefined = FFlag::DebugLuauGreedyGeneralization ? false : !hasFreeType(sig.signature);
if (sigFullyDefined)
emplaceType<BoundType>(asMutable(functionType), sig.signature);
@ -1440,7 +1471,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFunction* f
Checkpoint start = checkpoint(this);
FunctionSignature sig = checkFunctionSignature(scope, function->func, /* expectedType */ std::nullopt, function->name->location);
bool sigFullyDefined = !hasFreeType(sig.signature);
bool sigFullyDefined = FFlag::DebugLuauGreedyGeneralization ? false : !hasFreeType(sig.signature);
DefId def = dfg->getDef(function->name);
@ -1779,7 +1810,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeFunctio
GeneralizationConstraint{
generalizedTy,
sig.signature,
FFlag::LuauTrackInteriorFreeTypesOnScope ? std::vector<TypeId>{} : std::move(DEPRECATED_interiorTypes.back())
std::vector<TypeId>{},
}
);
@ -1788,7 +1819,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeFunctio
sig.signatureScope->interiorFreeTypes = std::move(interiorFreeTypes.back().types);
sig.signatureScope->interiorFreeTypePacks = std::move(interiorFreeTypes.back().typePacks);
}
else if (FFlag::LuauTrackInteriorFreeTypesOnScope)
else
sig.signatureScope->interiorFreeTypes = std::move(DEPRECATED_interiorTypes.back());
getMutable<BlockedType>(generalizedTy)->setOwner(gc);
@ -1852,71 +1883,71 @@ static bool isMetamethod(const Name& name)
name == "__idiv";
}
ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareClass* declaredClass)
ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareExternType* declaredExternType)
{
// If a class with the same name was already defined, we skip over
auto bindingIt = scope->exportedTypeBindings.find(declaredClass->name.value);
auto bindingIt = scope->exportedTypeBindings.find(declaredExternType->name.value);
if (bindingIt == scope->exportedTypeBindings.end())
return ControlFlow::None;
std::optional<TypeId> superTy = std::make_optional(builtinTypes->classType);
if (declaredClass->superName)
std::optional<TypeId> superTy = std::make_optional(builtinTypes->externType);
if (declaredExternType->superName)
{
Name superName = Name(declaredClass->superName->value);
Name superName = Name(declaredExternType->superName->value);
std::optional<TypeFun> lookupType = scope->lookupType(superName);
if (!lookupType)
{
reportError(declaredClass->location, UnknownSymbol{superName, UnknownSymbol::Type});
reportError(declaredExternType->location, UnknownSymbol{superName, UnknownSymbol::Type});
return ControlFlow::None;
}
// We don't have generic classes, so this assertion _should_ never be hit.
// We don't have generic extern types, so this assertion _should_ never be hit.
LUAU_ASSERT(lookupType->typeParams.size() == 0 && lookupType->typePackParams.size() == 0);
superTy = follow(lookupType->type);
if (!get<ClassType>(follow(*superTy)))
if (!get<ExternType>(follow(*superTy)))
{
reportError(
declaredClass->location,
GenericError{format("Cannot use non-class type '%s' as a superclass of class '%s'", superName.c_str(), declaredClass->name.value)}
declaredExternType->location,
GenericError{format("Cannot use non-class type '%s' as a superclass of class '%s'", superName.c_str(), declaredExternType->name.value)}
);
return ControlFlow::None;
}
}
Name className(declaredClass->name.value);
Name className(declaredExternType->name.value);
TypeId classTy = arena->addType(ClassType(className, {}, superTy, std::nullopt, {}, {}, module->name, declaredClass->location));
ClassType* ctv = getMutable<ClassType>(classTy);
TypeId externTy = arena->addType(ExternType(className, {}, superTy, std::nullopt, {}, {}, module->name, declaredExternType->location));
ExternType* etv = getMutable<ExternType>(externTy);
TypeId metaTy = arena->addType(TableType{TableState::Sealed, scope->level, scope.get()});
TableType* metatable = getMutable<TableType>(metaTy);
ctv->metatable = metaTy;
etv->metatable = metaTy;
TypeId classBindTy = bindingIt->second.type;
emplaceType<BoundType>(asMutable(classBindTy), classTy);
emplaceType<BoundType>(asMutable(classBindTy), externTy);
if (declaredClass->indexer)
if (declaredExternType->indexer)
{
RecursionCounter counter{&recursionCount};
if (recursionCount >= FInt::LuauCheckRecursionLimit)
{
reportCodeTooComplex(declaredClass->indexer->location);
reportCodeTooComplex(declaredExternType->indexer->location);
}
else
{
ctv->indexer = TableIndexer{
resolveType(scope, declaredClass->indexer->indexType, /* inTypeArguments */ false),
resolveType(scope, declaredClass->indexer->resultType, /* inTypeArguments */ false),
etv->indexer = TableIndexer{
resolveType(scope, declaredExternType->indexer->indexType, /* inTypeArguments */ false),
resolveType(scope, declaredExternType->indexer->resultType, /* inTypeArguments */ false),
};
}
}
for (const AstDeclaredClassProp& prop : declaredClass->props)
for (const AstDeclaredExternTypeProperty& prop : declaredExternType->props)
{
Name propName(prop.name.value);
TypeId propTy = resolveType(scope, prop.ty, /* inTypeArguments */ false);
@ -1930,7 +1961,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareClas
if (FunctionType* ftv = getMutable<FunctionType>(propTy))
{
ftv->argNames.insert(ftv->argNames.begin(), FunctionArgument{"self", {}});
ftv->argTypes = addTypePack({classTy}, ftv->argTypes);
ftv->argTypes = addTypePack({externTy}, ftv->argTypes);
ftv->hasSelf = true;
@ -1945,7 +1976,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareClas
}
}
TableType::Props& props = assignToMetatable ? metatable->props : ctv->props;
TableType::Props& props = assignToMetatable ? metatable->props : etv->props;
if (props.count(propName) == 0)
{
@ -1976,7 +2007,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareClas
}
else
{
reportError(declaredClass->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())});
}
}
}
@ -2008,7 +2039,8 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareFunc
funScope = childScope(global, scope);
TypePackId paramPack = resolveTypePack(funScope, global->params, /* inTypeArguments */ false);
TypePackId retPack = resolveTypePack(funScope, global->retTypes, /* inTypeArguments */ false);
TypePackId retPack = FFlag::LuauStoreReturnTypesAsPackOnAst ? resolveTypePack(funScope, global->retTypes, /* inTypeArguments */ false)
: resolveTypePack(funScope, global->retTypes_DEPRECATED, /* inTypeArguments */ false);
FunctionDefinition defn;
@ -2357,9 +2389,12 @@ InferencePack ConstraintGenerator::checkPack(const ScopePtr& scope, AstExprCall*
this,
[checkConstraint, callConstraint](const ConstraintPtr& constraint)
{
constraint->dependencies.emplace_back(checkConstraint);
if (!(FFlag::DebugLuauGreedyGeneralization && get<PrimitiveTypeConstraint>(*constraint)))
{
constraint->dependencies.emplace_back(checkConstraint);
callConstraint->dependencies.emplace_back(constraint.get());
callConstraint->dependencies.emplace_back(constraint.get());
}
}
);
@ -2464,8 +2499,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprConstantStrin
}
else
{
FreeType ft =
FFlag::LuauFreeTypesMustHaveBounds ? FreeType{scope.get(), builtinTypes->neverType, builtinTypes->unknownType} : FreeType{scope.get()};
FreeType ft = FreeType{scope.get(), builtinTypes->neverType, builtinTypes->unknownType};
ft.lowerBound = arena->addType(SingletonType{StringSingleton{std::string{string->value.data, string->value.size}}});
ft.upperBound = builtinTypes->stringType;
freeTy = arena->addType(ft);
@ -2492,8 +2526,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprConstantBool*
}
else
{
FreeType ft =
FFlag::LuauFreeTypesMustHaveBounds ? FreeType{scope.get(), builtinTypes->neverType, builtinTypes->unknownType} : FreeType{scope.get()};
FreeType ft = FreeType{scope.get(), builtinTypes->neverType, builtinTypes->unknownType};
ft.lowerBound = singletonType;
ft.upperBound = builtinTypes->booleanType;
freeTy = arena->addType(ft);
@ -2658,8 +2691,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprFunction* fun
GeneralizationConstraint{
generalizedTy,
sig.signature,
(FFlag::LuauNonReentrantGeneralization2 || FFlag::LuauTrackInteriorFreeTypesOnScope) ? std::vector<TypeId>{}
: std::move(DEPRECATED_interiorTypes.back())
std::vector<TypeId>{},
}
);
@ -2673,9 +2705,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprFunction* fun
}
else
{
if (FFlag::LuauTrackInteriorFreeTypesOnScope)
sig.signatureScope->interiorFreeTypes = std::move(DEPRECATED_interiorTypes.back());
sig.signatureScope->interiorFreeTypes = std::move(DEPRECATED_interiorTypes.back());
getMutable<BlockedType>(generalizedTy)->setOwner(gc);
DEPRECATED_interiorTypes.pop_back();
}
@ -2963,7 +2993,7 @@ std::tuple<TypeId, TypeId, RefinementId> ConstraintGenerator::checkBinary(
else if (typeguard->type == "userdata")
{
// For now, we don't really care about being accurate with userdata if the typeguard was using typeof.
discriminantTy = builtinTypes->classType;
discriminantTy = builtinTypes->externType;
}
else if (!typeguard->isTypeof && typeguard->type == "vector")
discriminantTy = builtinTypes->neverType; // TODO: figure out a way to deal with this quirky type
@ -2973,8 +3003,8 @@ std::tuple<TypeId, TypeId, RefinementId> ConstraintGenerator::checkBinary(
{
TypeId ty = follow(typeFun->type);
// We're only interested in the root class of any classes.
if (auto ctv = get<ClassType>(ty); ctv && (ctv->parent == builtinTypes->classType || hasTag(ty, kTypeofRootTag)))
// We're only interested in the root type of any extern type.
if (auto etv = get<ExternType>(ty); etv && (etv->parent == builtinTypes->externType || hasTag(ty, kTypeofRootTag)))
discriminantTy = ty;
}
@ -3047,7 +3077,7 @@ void ConstraintGenerator::visitLValue(const ScopePtr& scope, AstExprLocal* local
if (ty)
{
TypeIds* localDomain = localTypes.find(*ty);
if (localDomain)
if (localDomain && !(FFlag::LuauDoNotAddUpvalueTypesToLocalType && local->upvalue))
localDomain->insert(rhsType);
}
else
@ -3078,8 +3108,10 @@ void ConstraintGenerator::visitLValue(const ScopePtr& scope, AstExprLocal* local
if (annotatedTy)
addConstraint(scope, local->location, SubtypeConstraint{rhsType, *annotatedTy});
if (TypeIds* localDomain = localTypes.find(*ty))
localDomain->insert(rhsType);
// This is vestigial.
if (!FFlag::LuauDoNotAddUpvalueTypesToLocalType)
if (TypeIds* localDomain = localTypes.find(*ty))
localDomain->insert(rhsType);
}
void ConstraintGenerator::visitLValue(const ScopePtr& scope, AstExprGlobal* global, TypeId rhsType)
@ -3383,12 +3415,36 @@ ConstraintGenerator::FunctionSignature ConstraintGenerator::checkFunctionSignatu
LUAU_ASSERT(nullptr != varargPack);
if (FFlag::DebugLuauGreedyGeneralization)
{
// Some of the types in argTypes will eventually be generics, and some
// will not. The ones that are not generic will be pruned when
// GeneralizationConstraint dispatches.
genericTypes.insert(genericTypes.begin(), argTypes.begin(), argTypes.end());
varargPack = follow(varargPack);
returnType = follow(returnType);
if (varargPack == returnType)
genericTypePacks = {varargPack};
else
genericTypePacks = {varargPack, returnType};
}
// If there is both an annotation and an expected type, the annotation wins.
// Type checking will sort out any discrepancies later.
if (fn->returnAnnotation)
if (FFlag::LuauStoreReturnTypesAsPackOnAst && fn->returnAnnotation)
{
TypePackId annotatedRetType =
resolveTypePack(signatureScope, *fn->returnAnnotation, /* inTypeArguments */ false, /* replaceErrorWithFresh*/ true);
resolveTypePack(signatureScope, fn->returnAnnotation, /* 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 (!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.
@ -3639,8 +3695,16 @@ TypeId ConstraintGenerator::resolveFunctionType(
AstTypePackExplicit tempArgTypes{Location{}, fn->argTypes};
TypePackId argTypes = resolveTypePack_(signatureScope, &tempArgTypes, inTypeArguments, replaceErrorWithFresh);
AstTypePackExplicit tempRetTypes{Location{}, fn->returnTypes};
TypePackId returnTypes = resolveTypePack_(signatureScope, &tempRetTypes, inTypeArguments, replaceErrorWithFresh);
TypePackId returnTypes;
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
// how to quantify/instantiate it.
@ -3725,12 +3789,8 @@ TypeId ConstraintGenerator::resolveType_(const ScopePtr& scope, AstType* ty, boo
}
else
{
if (FFlag::LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
{
if (unionAnnotation->types.size == 1)
return resolveType_(scope, unionAnnotation->types.data[0], inTypeArguments);
}
if (unionAnnotation->types.size == 1)
return resolveType_(scope, unionAnnotation->types.data[0], inTypeArguments);
std::vector<TypeId> parts;
for (AstType* part : unionAnnotation->types)
{
@ -3759,12 +3819,8 @@ TypeId ConstraintGenerator::resolveType_(const ScopePtr& scope, AstType* ty, boo
}
else
{
if (FFlag::LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
{
if (intersectionAnnotation->types.size == 1)
return resolveType_(scope, intersectionAnnotation->types.data[0], inTypeArguments);
}
if (intersectionAnnotation->types.size == 1)
return resolveType_(scope, intersectionAnnotation->types.data[0], inTypeArguments);
std::vector<TypeId> parts;
for (AstType* part : intersectionAnnotation->types)
{
@ -4294,15 +4350,4 @@ TypeId ConstraintGenerator::simplifyUnion(const ScopePtr& scope, Location locati
return ::Luau::simplifyUnion(builtinTypes, arena, left, right).result;
}
std::vector<NotNull<Constraint>> borrowConstraints(const std::vector<ConstraintPtr>& constraints)
{
std::vector<NotNull<Constraint>> result;
result.reserve(constraints.size());
for (const auto& c : constraints)
result.emplace_back(c.get());
return result;
}
} // namespace Luau

View file

@ -11,7 +11,6 @@
#include "Luau/Location.h"
#include "Luau/ModuleResolver.h"
#include "Luau/OverloadResolution.h"
#include "Luau/Quantify.h"
#include "Luau/RecursionCounter.h"
#include "Luau/Simplify.h"
#include "Luau/TableLiteralInference.h"
@ -33,20 +32,20 @@ LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverIncludeDependencies)
LUAU_FASTFLAGVARIABLE(DebugLuauLogBindings)
LUAU_FASTINTVARIABLE(LuauSolverRecursionLimit, 500)
LUAU_FASTFLAGVARIABLE(DebugLuauEqSatSimplification)
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope)
LUAU_FASTFLAGVARIABLE(LuauTrackInteriorFreeTablesOnScope)
LUAU_FASTFLAGVARIABLE(LuauHasPropProperBlock)
LUAU_FASTFLAGVARIABLE(DebugLuauGreedyGeneralization)
LUAU_FASTFLAG(LuauSearchForRefineableType)
LUAU_FASTFLAG(LuauDeprecatedAttribute)
LUAU_FASTFLAG(LuauNonReentrantGeneralization2)
LUAU_FASTFLAG(LuauBidirectionalInferenceCollectIndexerTypes)
LUAU_FASTFLAG(LuauNewTypeFunReductionChecks2)
LUAU_FASTFLAGVARIABLE(LuauTrackInferredFunctionTypeFromCall)
LUAU_FASTFLAGVARIABLE(LuauAddCallConstraintForIterableFunctions)
namespace Luau
{
static void dump(ConstraintSolver* cs, ToStringOptions& opts);
size_t HashBlockedConstraintId::operator()(const BlockedConstraintId& bci) const
{
size_t result = 0;
@ -276,26 +275,6 @@ size_t HashInstantiationSignature::operator()(const InstantiationSignature& sign
return hash;
}
void dump(ConstraintSolver* cs, ToStringOptions& opts)
{
printf("constraints:\n");
for (NotNull<const Constraint> c : cs->unsolvedConstraints)
{
auto it = cs->blockedConstraints.find(c);
int blockCount = it == cs->blockedConstraints.end() ? 0 : int(it->second);
printf("\t%d\t%s\n", blockCount, toString(*c, opts).c_str());
if (FFlag::DebugLuauLogSolverIncludeDependencies)
{
for (NotNull<Constraint> dep : c->dependencies)
{
if (std::find(cs->unsolvedConstraints.begin(), cs->unsolvedConstraints.end(), dep) != cs->unsolvedConstraints.end())
printf("\t\t|\t%s\n", toString(*dep, opts).c_str());
}
}
}
}
struct InstantiationQueuer : TypeOnceVisitor
{
ConstraintSolver* solver;
@ -321,12 +300,44 @@ struct InstantiationQueuer : TypeOnceVisitor
return true;
}
bool visit(TypeId ty, const ClassType& ctv) override
bool visit(TypeId ty, const ExternType& etv) override
{
return false;
}
};
ConstraintSolver::ConstraintSolver(
NotNull<Normalizer> normalizer,
NotNull<Simplifier> simplifier,
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
ModuleName moduleName,
NotNull<ModuleResolver> moduleResolver,
std::vector<RequireCycle> requireCycles,
DcrLogger* logger,
NotNull<const DataFlowGraph> dfg,
TypeCheckLimits limits,
ConstraintSet constraintSet_
)
: arena(normalizer->arena)
, builtinTypes(normalizer->builtinTypes)
, normalizer(normalizer)
, simplifier(simplifier)
, typeFunctionRuntime(typeFunctionRuntime)
, constraintSet(std::move(constraintSet_))
, constraints(borrowConstraints(constraintSet.constraints))
, scopeToFunction(&constraintSet.scopeToFunction)
, rootScope(constraintSet.rootScope)
, currentModuleName(std::move(moduleName))
, dfg(dfg)
, moduleResolver(moduleResolver)
, requireCycles(std::move(requireCycles))
, logger(logger)
, limits(std::move(limits))
, opts{/*exhaustive*/ true}
{
initFreeTypeTracking();
}
ConstraintSolver::ConstraintSolver(
NotNull<Normalizer> normalizer,
NotNull<Simplifier> simplifier,
@ -346,6 +357,7 @@ ConstraintSolver::ConstraintSolver(
, normalizer(normalizer)
, simplifier(simplifier)
, typeFunctionRuntime(typeFunctionRuntime)
, constraintSet{rootScope}
, constraints(std::move(constraints))
, scopeToFunction(scopeToFunction)
, rootScope(rootScope)
@ -355,33 +367,9 @@ ConstraintSolver::ConstraintSolver(
, requireCycles(std::move(requireCycles))
, logger(logger)
, limits(std::move(limits))
, opts{/*exhaustive*/ true}
{
opts.exhaustive = true;
for (NotNull<Constraint> c : this->constraints)
{
unsolvedConstraints.emplace_back(c);
auto maybeMutatedTypesPerConstraint = c->getMaybeMutatedFreeTypes();
for (auto ty : maybeMutatedTypesPerConstraint)
{
auto [refCount, _] = unresolvedConstraints.try_insert(ty, 0);
refCount += 1;
if (FFlag::DebugLuauGreedyGeneralization)
{
auto [it, fresh] = mutatedFreeTypeToConstraint.try_emplace(ty, DenseHashSet<const Constraint*>{nullptr});
it->second.insert(c.get());
}
}
maybeMutatedFreeTypes.emplace(c, maybeMutatedTypesPerConstraint);
for (NotNull<const Constraint> dep : c->dependencies)
{
block(dep, c);
}
}
initFreeTypeTracking();
}
void ConstraintSolver::randomize(unsigned seed)
@ -426,6 +414,18 @@ void ConstraintSolver::run()
logger->captureInitialSolverState(rootScope, unsolvedConstraints);
}
// Free types that have no constraints at all can be generalized right away.
if (FFlag::DebugLuauGreedyGeneralization)
{
for (TypeId ty : constraintSet.freeTypes)
{
if (auto it = mutatedFreeTypeToConstraint.find(ty); it == mutatedFreeTypeToConstraint.end() || it->second.empty())
generalizeOneType(ty);
}
}
constraintSet.freeTypes.clear();
auto runSolverPass = [&](bool force)
{
bool progress = false;
@ -655,12 +655,39 @@ struct TypeSearcher : TypeVisitor
// }
bool visit(TypeId ty, const ClassType&) override
bool visit(TypeId ty, const ExternType&) override
{
return false;
}
};
void ConstraintSolver::initFreeTypeTracking()
{
for (NotNull<Constraint> c : this->constraints)
{
unsolvedConstraints.emplace_back(c);
auto maybeMutatedTypesPerConstraint = c->getMaybeMutatedFreeTypes();
for (auto ty : maybeMutatedTypesPerConstraint)
{
auto [refCount, _] = unresolvedConstraints.try_insert(ty, 0);
refCount += 1;
if (FFlag::DebugLuauGreedyGeneralization)
{
auto [it, fresh] = mutatedFreeTypeToConstraint.try_emplace(ty, nullptr);
it->second.insert(c.get());
}
}
maybeMutatedFreeTypes.emplace(c, maybeMutatedTypesPerConstraint);
for (NotNull<const Constraint> dep : c->dependencies)
{
block(dep, c);
}
}
}
void ConstraintSolver::generalizeOneType(TypeId ty)
{
ty = follow(ty);
@ -858,62 +885,53 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNull<co
bind(constraint, c.generalizedType, builtinTypes->errorRecoveryType());
}
if (FFlag::LuauTrackInteriorFreeTypesOnScope)
// We check if this member is initialized and then access it, but
// clang-tidy doesn't understand this is safe.
if (constraint->scope->interiorFreeTypes)
{
// We check if this member is initialized and then access it, but
// clang-tidy doesn't understand this is safe.
if (constraint->scope->interiorFreeTypes)
for (TypeId ty : *constraint->scope->interiorFreeTypes) // NOLINT(bugprone-unchecked-optional-access)
{
for (TypeId ty : *constraint->scope->interiorFreeTypes) // NOLINT(bugprone-unchecked-optional-access)
if (FFlag::LuauNonReentrantGeneralization2)
{
if (FFlag::LuauNonReentrantGeneralization2)
ty = follow(ty);
if (auto freeTy = get<FreeType>(ty))
{
ty = follow(ty);
if (auto freeTy = get<FreeType>(ty))
{
GeneralizationParams<TypeId> params;
params.foundOutsideFunctions = true;
params.useCount = 1;
params.polarity = freeTy->polarity;
GeneralizationParams<TypeId> params;
params.foundOutsideFunctions = true;
params.useCount = 1;
params.polarity = freeTy->polarity;
GeneralizationResult<TypeId> res = generalizeType(arena, builtinTypes, constraint->scope, ty, params);
if (res.resourceLimitsExceeded)
reportError(CodeTooComplex{}, constraint->scope->location); // FIXME: We don't have a very good location for this.
}
else if (get<TableType>(ty))
sealTable(constraint->scope, ty);
GeneralizationResult<TypeId> res = generalizeType(arena, builtinTypes, constraint->scope, ty, params);
if (res.resourceLimitsExceeded)
reportError(CodeTooComplex{}, constraint->scope->location); // FIXME: We don't have a very good location for this.
}
else
generalize(NotNull{arena}, builtinTypes, constraint->scope, generalizedTypes, ty);
else if (get<TableType>(ty))
sealTable(constraint->scope, ty);
}
else
generalize(NotNull{arena}, builtinTypes, constraint->scope, generalizedTypes, ty);
}
}
if (FFlag::LuauNonReentrantGeneralization2)
if (FFlag::LuauNonReentrantGeneralization2)
{
if (constraint->scope->interiorFreeTypePacks)
{
if (constraint->scope->interiorFreeTypePacks)
for (TypePackId tp : *constraint->scope->interiorFreeTypePacks) // NOLINT(bugprone-unchecked-optional-access)
{
for (TypePackId tp : *constraint->scope->interiorFreeTypePacks) // NOLINT(bugprone-unchecked-optional-access)
tp = follow(tp);
if (auto freeTp = get<FreeTypePack>(tp))
{
tp = follow(tp);
if (auto freeTp = get<FreeTypePack>(tp))
{
GeneralizationParams<TypePackId> params;
params.foundOutsideFunctions = true;
params.useCount = 1;
params.polarity = freeTp->polarity;
LUAU_ASSERT(isKnown(params.polarity));
generalizeTypePack(arena, builtinTypes, constraint->scope, tp, params);
}
GeneralizationParams<TypePackId> params;
params.foundOutsideFunctions = true;
params.useCount = 1;
params.polarity = freeTp->polarity;
LUAU_ASSERT(isKnown(params.polarity));
generalizeTypePack(arena, builtinTypes, constraint->scope, tp, params);
}
}
}
}
else
{
for (TypeId ty : c.interiorTypes)
generalize(NotNull{arena}, builtinTypes, constraint->scope, generalizedTypes, ty);
}
return true;
}
@ -989,16 +1007,12 @@ bool ConstraintSolver::tryDispatch(const IterableConstraint& c, NotNull<const Co
{
TypeId keyTy = freshType(arena, builtinTypes, constraint->scope, Polarity::Mixed);
TypeId valueTy = freshType(arena, builtinTypes, constraint->scope, Polarity::Mixed);
if (FFlag::LuauTrackInteriorFreeTypesOnScope)
{
trackInteriorFreeType(constraint->scope, keyTy);
trackInteriorFreeType(constraint->scope, valueTy);
}
trackInteriorFreeType(constraint->scope, keyTy);
trackInteriorFreeType(constraint->scope, valueTy);
TypeId tableTy =
arena->addType(TableType{TableType::Props{}, TableIndexer{keyTy, valueTy}, TypeLevel{}, constraint->scope, TableState::Free});
if (FFlag::LuauTrackInteriorFreeTypesOnScope && FFlag::LuauTrackInteriorFreeTablesOnScope)
trackInteriorFreeType(constraint->scope, tableTy);
trackInteriorFreeType(constraint->scope, tableTy);
unify(constraint, nextTy, tableTy);
@ -1322,29 +1336,14 @@ void ConstraintSolver::fillInDiscriminantTypes(NotNull<const Constraint> constra
if (!ty)
continue;
if (FFlag::LuauSearchForRefineableType)
{
if (isBlocked(*ty))
// We bind any unused discriminants to the `*no-refine*` type indicating that it can be safely ignored.
emplaceType<BoundType>(asMutable(follow(*ty)), builtinTypes->noRefineType);
// We also need to unconditionally unblock these types, otherwise
// you end up with funky looking "Blocked on *no-refine*."
unblock(*ty, constraint->location);
}
else
{
// If the discriminant type has been transmuted, we need to unblock them.
if (!isBlocked(*ty))
{
unblock(*ty, constraint->location);
continue;
}
if (isBlocked(*ty))
// We bind any unused discriminants to the `*no-refine*` type indicating that it can be safely ignored.
emplaceType<BoundType>(asMutable(follow(*ty)), builtinTypes->noRefineType);
}
// We also need to unconditionally unblock these types, otherwise
// you end up with funky looking "Blocked on *no-refine*."
unblock(*ty, constraint->location);
}
}
@ -1457,9 +1456,9 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
if (ftv)
{
if (ftv->magic)
if (ftv->magic && c.callSite)
{
usedMagic = ftv->magic->infer(MagicFunctionCallContext{NotNull{this}, constraint, c.callSite, c.argsPack, result});
usedMagic = ftv->magic->infer(MagicFunctionCallContext{NotNull{this}, constraint, NotNull{c.callSite}, c.argsPack, result});
ftv->magic->refine(MagicRefinementContext{constraint->scope, c.callSite, c.discriminantTypes});
}
}
@ -1534,7 +1533,7 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
// This can potentially contain free types if the return type of
// `inferredTy` is never unified elsewhere.
if (FFlag::LuauTrackInteriorFreeTypesOnScope && FFlag::LuauTrackInferredFunctionTypeFromCall)
if (FFlag::LuauTrackInferredFunctionTypeFromCall)
trackInteriorFreeType(constraint->scope, inferredTy);
unblock(c.result, constraint->location);
@ -1903,7 +1902,7 @@ bool ConstraintSolver::tryDispatchHasIndexer(
}
else if (auto mt = get<MetatableType>(subjectType))
return tryDispatchHasIndexer(recursionDepth, constraint, mt->table, indexType, resultType, seen);
else if (auto ct = get<ClassType>(subjectType))
else if (auto ct = get<ExternType>(subjectType))
{
if (auto indexer = ct->indexer)
{
@ -2065,9 +2064,9 @@ bool ConstraintSolver::tryDispatch(const AssignPropConstraint& c, NotNull<const
// Important: In every codepath through this function, the type `c.propType`
// must be bound to something, even if it's just the errorType.
if (auto lhsClass = get<ClassType>(lhsType))
if (auto lhsExternType = get<ExternType>(lhsType))
{
const Property* prop = lookupClassProp(lhsClass, propName);
const Property* prop = lookupExternTypeProp(lhsExternType, propName);
if (!prop || !prop->writeTy.has_value())
{
bind(constraint, c.propType, builtinTypes->anyType);
@ -2082,25 +2081,61 @@ bool ConstraintSolver::tryDispatch(const AssignPropConstraint& c, NotNull<const
if (auto lhsFree = getMutable<FreeType>(lhsType))
{
auto lhsFreeUpperBound = follow(lhsFree->upperBound);
if (get<TableType>(lhsFreeUpperBound) || get<MetatableType>(lhsFreeUpperBound))
lhsType = lhsFreeUpperBound;
else
{
TypeId newUpperBound = arena->addType(TableType{TableState::Free, TypeLevel{}, constraint->scope});
if (FFlag::LuauTrackInteriorFreeTypesOnScope && FFlag::LuauTrackInteriorFreeTablesOnScope)
if (FFlag::DebugLuauGreedyGeneralization)
{
const auto [blocked, maybeTy, isIndex] = lookupTableProp(constraint, lhsType, propName, ValueContext::LValue);
if (!blocked.empty())
{
for (TypeId t : blocked)
block(t, constraint);
return false;
}
else if (maybeTy)
{
bind(constraint, c.propType, isIndex ? arena->addType(UnionType{{*maybeTy, builtinTypes->nilType}}) : *maybeTy);
unify(constraint, rhsType, *maybeTy);
return true;
}
else
{
TypeId newUpperBound = arena->addType(TableType{TableState::Free, TypeLevel{}, constraint->scope});
trackInteriorFreeType(constraint->scope, newUpperBound);
TableType* upperTable = getMutable<TableType>(newUpperBound);
LUAU_ASSERT(upperTable);
TableType* upperTable = getMutable<TableType>(newUpperBound);
LUAU_ASSERT(upperTable);
upperTable->props[c.propName] = rhsType;
upperTable->props[c.propName] = rhsType;
// Food for thought: Could we block if simplification encounters a blocked type?
lhsFree->upperBound = simplifyIntersection(constraint->scope, constraint->location, lhsFreeUpperBound, newUpperBound);
// Food for thought: Could we block if simplification encounters a blocked type?
lhsFree->upperBound = simplifyIntersection(constraint->scope, constraint->location, lhsFreeUpperBound, newUpperBound);
bind(constraint, c.propType, rhsType);
return true;
bind(constraint, c.propType, rhsType);
return true;
}
}
else
{
if (get<TableType>(lhsFreeUpperBound) || get<MetatableType>(lhsFreeUpperBound))
lhsType = lhsFreeUpperBound;
else
{
TypeId newUpperBound = arena->addType(TableType{TableState::Free, TypeLevel{}, constraint->scope});
trackInteriorFreeType(constraint->scope, newUpperBound);
TableType* upperTable = getMutable<TableType>(newUpperBound);
LUAU_ASSERT(upperTable);
upperTable->props[c.propName] = rhsType;
// Food for thought: Could we block if simplification encounters a blocked type?
lhsFree->upperBound = simplifyIntersection(constraint->scope, constraint->location, lhsFreeUpperBound, newUpperBound);
bind(constraint, c.propType, rhsType);
return true;
}
}
}
@ -2252,20 +2287,20 @@ bool ConstraintSolver::tryDispatch(const AssignIndexConstraint& c, NotNull<const
return *res;
}
if (auto lhsClass = get<ClassType>(lhsType))
if (auto lhsExternType = get<ExternType>(lhsType))
{
while (true)
{
if (lhsClass->indexer)
if (lhsExternType->indexer)
{
unify(constraint, indexType, lhsClass->indexer->indexType);
unify(constraint, rhsType, lhsClass->indexer->indexResultType);
bind(constraint, c.propType, arena->addType(UnionType{{lhsClass->indexer->indexResultType, builtinTypes->nilType}}));
unify(constraint, indexType, lhsExternType->indexer->indexType);
unify(constraint, rhsType, lhsExternType->indexer->indexResultType);
bind(constraint, c.propType, arena->addType(UnionType{{lhsExternType->indexer->indexResultType, builtinTypes->nilType}}));
return true;
}
if (lhsClass->parent)
lhsClass = get<ClassType>(lhsClass->parent);
if (lhsExternType->parent)
lhsExternType = get<ExternType>(lhsExternType->parent);
else
break;
}
@ -2292,7 +2327,7 @@ bool ConstraintSolver::tryDispatch(const AssignIndexConstraint& c, NotNull<const
parts.insert(rhsType);
}
}
else if (auto cls = get<ClassType>(follow(t)))
else if (auto cls = get<ExternType>(follow(t)))
{
while (true)
{
@ -2304,7 +2339,7 @@ bool ConstraintSolver::tryDispatch(const AssignIndexConstraint& c, NotNull<const
}
if (cls->parent)
cls = get<ClassType>(cls->parent);
cls = get<ExternType>(cls->parent);
else
break;
}
@ -2355,8 +2390,7 @@ bool ConstraintSolver::tryDispatch(const UnpackConstraint& c, NotNull<const Cons
// constitute any meaningful constraint, so we replace it
// with a free type.
TypeId f = freshType(arena, builtinTypes, constraint->scope, Polarity::Positive); // FIXME? Is this the right polarity?
if (FFlag::LuauTrackInteriorFreeTypesOnScope)
trackInteriorFreeType(constraint->scope, f);
trackInteriorFreeType(constraint->scope, f);
shiftReferences(resultTy, f);
emplaceType<BoundType>(asMutable(resultTy), f);
}
@ -2499,11 +2533,8 @@ bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const Iterabl
{
TypeId keyTy = freshType(arena, builtinTypes, constraint->scope, Polarity::Mixed);
TypeId valueTy = freshType(arena, builtinTypes, constraint->scope, Polarity::Mixed);
if (FFlag::LuauTrackInteriorFreeTypesOnScope)
{
trackInteriorFreeType(constraint->scope, keyTy);
trackInteriorFreeType(constraint->scope, valueTy);
}
trackInteriorFreeType(constraint->scope, keyTy);
trackInteriorFreeType(constraint->scope, valueTy);
TypeId tableTy = arena->addType(TableType{TableState::Sealed, {}, constraint->scope});
getMutable<TableType>(tableTy)->indexer = TableIndexer{keyTy, valueTy};
@ -2653,33 +2684,53 @@ bool ConstraintSolver::tryDispatchIterableFunction(TypeId nextTy, TypeId tableTy
const FunctionType* nextFn = get<FunctionType>(nextTy);
// If this does not hold, we should've never called `tryDispatchIterableFunction` in the first place.
LUAU_ASSERT(nextFn);
const TypePackId nextRetPack = nextFn->retTypes;
// the type of the `nextAstFragment` is the `nextTy`.
(*c.astForInNextTypes)[c.nextAstFragment] = nextTy;
auto it = begin(nextRetPack);
std::vector<TypeId> modifiedNextRetHead;
// The first value is never nil in the context of the loop, even if it's nil
// in the next function's return type, because the loop will not advance if
// it's nil.
if (it != end(nextRetPack))
if (FFlag::LuauAddCallConstraintForIterableFunctions)
{
TypeId firstRet = *it;
TypeId modifiedFirstRet = stripNil(builtinTypes, *arena, firstRet);
modifiedNextRetHead.push_back(modifiedFirstRet);
++it;
// Construct a FunctionCallConstraint, to help us learn about the type of the loop variables being assigned to in this iterable
TypePackId tableTyPack = arena->addTypePack({tableTy});
TypePackId variablesPack = arena->addTypePack(BlockedTypePack{});
auto callConstraint = pushConstraint(constraint->scope, constraint->location, FunctionCallConstraint{nextTy, tableTyPack, variablesPack});
getMutable<BlockedTypePack>(variablesPack)->owner = callConstraint.get();
auto unpackConstraint = unpackAndAssign(c.variables, variablesPack, constraint);
inheritBlocks(constraint, callConstraint);
inheritBlocks(unpackConstraint, callConstraint);
}
else
{
const TypePackId nextRetPack = nextFn->retTypes;
TypePackIterator it = begin(nextRetPack);
std::vector<TypeId> modifiedNextRetHead;
for (; it != end(nextRetPack); ++it)
modifiedNextRetHead.push_back(*it);
// The first value is never nil in the context of the loop, even if it's nil
// in the next function's return type, because the loop will not advance if
// it's nil.
if (it != end(nextRetPack))
{
TypeId firstRet = *it;
TypeId modifiedFirstRet = stripNil(builtinTypes, *arena, firstRet);
modifiedNextRetHead.push_back(modifiedFirstRet);
++it;
}
TypePackId modifiedNextRetPack = arena->addTypePack(std::move(modifiedNextRetHead), it.tail());
for (; it != end(nextRetPack); ++it)
modifiedNextRetHead.push_back(*it);
auto unpackConstraint = unpackAndAssign(c.variables, modifiedNextRetPack, constraint);
TypePackId modifiedNextRetPack = arena->addTypePack(std::move(modifiedNextRetHead), it.tail());
inheritBlocks(constraint, unpackConstraint);
auto unpackConstraint = unpackAndAssign(c.variables, modifiedNextRetPack, constraint);
inheritBlocks(constraint, unpackConstraint);
}
return true;
}
@ -2760,8 +2811,7 @@ TablePropLookupResult ConstraintSolver::lookupTableProp(
if (ttv->state == TableState::Free)
{
TypeId result = freshType(arena, builtinTypes, ttv->scope, Polarity::Mixed);
if (FFlag::LuauTrackInteriorFreeTypesOnScope)
trackInteriorFreeType(ttv->scope, result);
trackInteriorFreeType(ttv->scope, result);
switch (context)
{
case ValueContext::RValue:
@ -2835,9 +2885,9 @@ TablePropLookupResult ConstraintSolver::lookupTableProp(
else if (get<MetatableType>(mtt))
return lookupTableProp(constraint, mtt, propName, context, inConditional, suppressSimplification, seen);
}
else if (auto ct = get<ClassType>(subjectType))
else if (auto ct = get<ExternType>(subjectType))
{
if (auto p = lookupClassProp(ct, propName))
if (auto p = lookupExternTypeProp(ct, propName))
return {{}, context == ValueContext::RValue ? p->readTy : p->writeTy};
if (ct->indexer)
{
@ -2859,24 +2909,34 @@ TablePropLookupResult ConstraintSolver::lookupTableProp(
{
const TypeId upperBound = follow(ft->upperBound);
if (get<TableType>(upperBound) || get<PrimitiveType>(upperBound))
return lookupTableProp(constraint, upperBound, propName, context, inConditional, suppressSimplification, seen);
if (FFlag::DebugLuauGreedyGeneralization)
{
if (get<TableType>(upperBound) || get<PrimitiveType>(upperBound))
{
TablePropLookupResult res = lookupTableProp(constraint, upperBound, propName, context, inConditional, suppressSimplification, seen);
// If the upper bound is a table that already has the property, we don't need to extend its bounds.
if (res.propType || get<PrimitiveType>(upperBound))
return res;
}
}
else
{
if (get<TableType>(upperBound) || get<PrimitiveType>(upperBound))
return lookupTableProp(constraint, upperBound, propName, context, inConditional, suppressSimplification, seen);
}
// TODO: The upper bound could be an intersection that contains suitable tables or classes.
// TODO: The upper bound could be an intersection that contains suitable tables or extern types.
NotNull<Scope> scope{ft->scope};
const TypeId newUpperBound = arena->addType(TableType{TableState::Free, TypeLevel{}, scope});
if (FFlag::LuauTrackInteriorFreeTypesOnScope && FFlag::LuauTrackInteriorFreeTablesOnScope)
trackInteriorFreeType(constraint->scope, newUpperBound);
trackInteriorFreeType(constraint->scope, newUpperBound);
TableType* tt = getMutable<TableType>(newUpperBound);
LUAU_ASSERT(tt);
TypeId propType = freshType(arena, builtinTypes, scope, Polarity::Mixed);
if (FFlag::LuauTrackInteriorFreeTypesOnScope)
trackInteriorFreeType(scope, propType);
trackInteriorFreeType(scope, propType);
switch (context)
{
@ -3095,7 +3155,7 @@ struct Blocker : TypeOnceVisitor
return false;
}
bool visit(TypeId ty, const ClassType&) override
bool visit(TypeId ty, const ExternType&) override
{
return false;
}
@ -3471,4 +3531,35 @@ LUAU_NOINLINE void ConstraintSolver::throwUserCancelError() const
template bool ConstraintSolver::unify(NotNull<const Constraint> constraint, TypeId subTy, TypeId superTy);
template bool ConstraintSolver::unify(NotNull<const Constraint> constraint, TypePackId subTy, TypePackId superTy);
std::vector<NotNull<Constraint>> borrowConstraints(const std::vector<ConstraintPtr>& constraints)
{
std::vector<NotNull<Constraint>> result;
result.reserve(constraints.size());
for (const auto& c : constraints)
result.emplace_back(c.get());
return result;
}
void dump(ConstraintSolver* cs, ToStringOptions& opts)
{
printf("constraints:\n");
for (NotNull<const Constraint> c : cs->unsolvedConstraints)
{
auto it = cs->blockedConstraints.find(c);
int blockCount = it == cs->blockedConstraints.end() ? 0 : int(it->second);
printf("\t%d\t%s\n", blockCount, toString(*c, opts).c_str());
if (FFlag::DebugLuauLogSolverIncludeDependencies)
{
for (NotNull<Constraint> dep : c->dependencies)
{
if (std::find(cs->unsolvedConstraints.begin(), cs->unsolvedConstraints.end(), dep) != cs->unsolvedConstraints.end())
printf("\t\t|\t%s\n", toString(*dep, opts).c_str());
}
}
}
}
} // namespace Luau

View file

@ -16,6 +16,9 @@ LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAGVARIABLE(LuauPreprocessTypestatedArgument)
LUAU_FASTFLAGVARIABLE(LuauDfgScopeStackTrueReset)
LUAU_FASTFLAGVARIABLE(LuauDfgScopeStackNotNull)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
LUAU_FASTFLAGVARIABLE(LuauDoNotAddUpvalueTypesToLocalType)
LUAU_FASTFLAGVARIABLE(LuauDfgIfBlocksShouldRespectControlFlow)
namespace Luau
{
@ -470,7 +473,7 @@ ControlFlow DataFlowGraphBuilder::visit(AstStat* s)
return visit(d);
else if (auto d = s->as<AstStatDeclareFunction>())
return visit(d);
else if (auto d = s->as<AstStatDeclareClass>())
else if (auto d = s->as<AstStatDeclareExternType>())
return visit(d);
else if (auto error = s->as<AstStatError>())
return visit(error);
@ -499,12 +502,26 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatIf* i)
}
DfgScope* scope = FFlag::LuauDfgScopeStackNotNull ? currentScope() : currentScope_DEPRECATED();
if (thencf != ControlFlow::None && elsecf == ControlFlow::None)
join(scope, scope, elseScope);
else if (thencf == ControlFlow::None && elsecf != ControlFlow::None)
join(scope, thenScope, scope);
else if ((thencf | elsecf) == ControlFlow::None)
join(scope, thenScope, elseScope);
if (FFlag::LuauDfgIfBlocksShouldRespectControlFlow)
{
// If the control flow from the `if` or `else` block is non-linear,
// then we should assume that the _other_ branch is the one taken.
if (thencf != ControlFlow::None && elsecf == ControlFlow::None)
scope->inherit(elseScope);
else if (thencf == ControlFlow::None && elsecf != ControlFlow::None)
scope->inherit(thenScope);
else if ((thencf | elsecf) == ControlFlow::None)
join(scope, thenScope, elseScope);
}
else
{
if (thencf != ControlFlow::None && elsecf == ControlFlow::None)
join(scope, scope, elseScope);
else if (thencf == ControlFlow::None && elsecf != ControlFlow::None)
join(scope, thenScope, scope);
else if ((thencf | elsecf) == ControlFlow::None)
join(scope, thenScope, elseScope);
}
if (thencf == elsecf)
return thencf;
@ -808,12 +825,15 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatDeclareFunction* d)
visitGenerics(d->generics);
visitGenericPacks(d->genericPacks);
visitTypeList(d->params);
visitTypeList(d->retTypes);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
visitTypePack(d->retTypes);
else
visitTypeList(d->retTypes_DEPRECATED);
return ControlFlow::None;
}
ControlFlow DataFlowGraphBuilder::visit(AstStatDeclareClass* d)
ControlFlow DataFlowGraphBuilder::visit(AstStatDeclareExternType* d)
{
// This declaration does not "introduce" any bindings in value namespace,
// so there's no symbolic value to begin with. We'll traverse the properties
@ -821,7 +841,7 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatDeclareClass* d)
DfgScope* unreachable = makeChildScope();
PushScope ps{scopeStack, unreachable};
for (AstDeclaredClassProp prop : d->props)
for (AstDeclaredExternTypeProperty prop : d->props)
visitType(prop.ty);
return ControlFlow::None;
@ -1032,8 +1052,16 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprFunction* f)
if (f->varargAnnotation)
visitTypePack(f->varargAnnotation);
if (f->returnAnnotation)
visitTypeList(*f->returnAnnotation);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
if (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
// visible to the beginning of the function, so statically speaking, the body of the function has an exit point
@ -1151,7 +1179,7 @@ DefId DataFlowGraphBuilder::visitLValue(AstExprLocal* l, DefId incomingDef)
DfgScope* scope = FFlag::LuauDfgScopeStackNotNull ? currentScope() : currentScope_DEPRECATED();
// In order to avoid alias tracking, we need to clip the reference to the parent def.
if (scope->canUpdateDefinition(l->local))
if (scope->canUpdateDefinition(l->local) && !(FFlag::LuauDoNotAddUpvalueTypesToLocalType && l->upvalue))
{
DefId updated = defArena->freshCell(l->local, l->location, containsSubscriptedDefinition(incomingDef));
scope->bindings[l->local] = updated;
@ -1275,7 +1303,10 @@ void DataFlowGraphBuilder::visitType(AstTypeFunction* f)
visitGenerics(f->generics);
visitGenericPacks(f->genericPacks);
visitTypeList(f->argTypes);
visitTypeList(f->returnTypes);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
visitTypePack(f->returnTypes);
else
visitTypeList(f->returnTypes_DEPRECATED);
}
void DataFlowGraphBuilder::visitType(AstTypeTypeof* t)

View file

@ -277,7 +277,7 @@ static DifferResult diffSingleton(DifferEnvironment& env, TypeId left, TypeId ri
static DifferResult diffFunction(DifferEnvironment& env, TypeId left, TypeId right);
static DifferResult diffGeneric(DifferEnvironment& env, TypeId left, TypeId right);
static DifferResult diffNegation(DifferEnvironment& env, TypeId left, TypeId right);
static DifferResult diffClass(DifferEnvironment& env, TypeId left, TypeId right);
static DifferResult diffExternType(DifferEnvironment& env, TypeId left, TypeId right);
struct FindSeteqCounterexampleResult
{
// nullopt if no counterexample found
@ -481,14 +481,14 @@ static DifferResult diffNegation(DifferEnvironment& env, TypeId left, TypeId rig
return differResult;
}
static DifferResult diffClass(DifferEnvironment& env, TypeId left, TypeId right)
static DifferResult diffExternType(DifferEnvironment& env, TypeId left, TypeId right)
{
const ClassType* leftClass = get<ClassType>(left);
const ClassType* rightClass = get<ClassType>(right);
LUAU_ASSERT(leftClass);
LUAU_ASSERT(rightClass);
const ExternType* leftExternType = get<ExternType>(left);
const ExternType* rightExternType = get<ExternType>(right);
LUAU_ASSERT(leftExternType);
LUAU_ASSERT(rightExternType);
if (leftClass == rightClass)
if (leftExternType == rightExternType)
{
return DifferResult{};
}
@ -651,9 +651,9 @@ static DifferResult diffUsingEnv(DifferEnvironment& env, TypeId left, TypeId rig
{
return diffNegation(env, left, right);
}
else if (auto lc = get<ClassType>(left))
else if (auto lc = get<ExternType>(left))
{
return diffClass(env, left, right);
return diffExternType(env, left, right);
}
throw InternalCompilerError{"Unimplemented Simple TypeId variant for diffing"};
@ -960,7 +960,7 @@ bool isSimple(TypeId ty)
{
ty = follow(ty);
// TODO: think about GenericType, etc.
return get<PrimitiveType>(ty) || get<SingletonType>(ty) || get<AnyType>(ty) || get<NegationType>(ty) || get<ClassType>(ty) ||
return get<PrimitiveType>(ty) || get<SingletonType>(ty) || get<AnyType>(ty) || get<NegationType>(ty) || get<ExternType>(ty) ||
get<UnknownType>(ty) || get<NeverType>(ty);
}

View file

@ -1,6 +1,9 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/BuiltinDefinitions.h"
LUAU_FASTFLAG(LuauDeclareExternType)
LUAU_FASTFLAG(LuauTypeFunOptional)
namespace Luau
{
@ -259,7 +262,37 @@ declare buffer: {
)BUILTIN_SRC";
static const std::string kBuiltinDefinitionVectorSrc = R"BUILTIN_SRC(
static const std::string kBuiltinDefinitionVectorSrc = (FFlag::LuauDeclareExternType)
? R"BUILTIN_SRC(
-- While vector would have been better represented as a built-in primitive type, type solver extern type handling covers most of the properties
declare extern type vector with
x: number
y: number
z: number
end
declare vector: {
create: @checked (x: number, y: number, z: number?) -> vector,
magnitude: @checked (vec: vector) -> number,
normalize: @checked (vec: vector) -> vector,
cross: @checked (vec1: vector, vec2: vector) -> vector,
dot: @checked (vec1: vector, vec2: vector) -> number,
angle: @checked (vec1: vector, vec2: vector, axis: vector?) -> number,
floor: @checked (vec: vector) -> vector,
ceil: @checked (vec: vector) -> vector,
abs: @checked (vec: vector) -> vector,
sign: @checked (vec: vector) -> vector,
clamp: @checked (vec: vector, min: vector, max: vector) -> vector,
max: @checked (vector, ...vector) -> vector,
min: @checked (vector, ...vector) -> vector,
zero: vector,
one: vector,
}
)BUILTIN_SRC"
: R"BUILTIN_SRC(
-- While vector would have been better represented as a built-in primitive type, type solver class handling covers most of the properties
declare class vector
@ -307,11 +340,11 @@ std::string getBuiltinDefinitionSource()
}
// TODO: split into separate tagged unions when the new solver can appropriately handle that.
static const std::string kBuiltinDefinitionTypesSrc = R"BUILTIN_SRC(
static const std::string kBuiltinDefinitionTypeMethodSrc = R"BUILTIN_SRC(
export type type = {
tag: "nil" | "unknown" | "never" | "any" | "boolean" | "number" | "string" | "buffer" | "thread" |
"singleton" | "negation" | "union" | "intesection" | "table" | "function" | "class" | "generic",
"singleton" | "negation" | "union" | "intersection" | "table" | "function" | "class" | "generic",
is: (self: type, arg: string) -> boolean,
@ -358,6 +391,10 @@ export type type = {
ispack: (self: type) -> boolean,
}
)BUILTIN_SRC";
static const std::string kBuiltinDefinitionTypesLibSrc = R"BUILTIN_SRC(
declare types: {
unknown: type,
never: type,
@ -377,12 +414,44 @@ declare types: {
newfunction: @checked (parameters: { head: {type}?, tail: type? }?, returns: { head: {type}?, tail: type? }?, generics: {type}?) -> type,
copy: @checked (arg: type) -> type,
}
)BUILTIN_SRC";
static const std::string kBuiltinDefinitionTypesLibWithOptionalSrc = R"BUILTIN_SRC(
declare types: {
unknown: type,
never: type,
any: type,
boolean: type,
number: type,
string: type,
thread: type,
buffer: type,
singleton: @checked (arg: string | boolean | nil) -> type,
optional: @checked (arg: type) -> type,
generic: @checked (name: string, ispack: boolean?) -> type,
negationof: @checked (arg: type) -> type,
unionof: @checked (...type) -> type,
intersectionof: @checked (...type) -> type,
newtable: @checked (props: {[type]: type} | {[type]: { read: type, write: type } } | nil, indexer: { index: type, readresult: type, writeresult: type }?, metatable: type?) -> type,
newfunction: @checked (parameters: { head: {type}?, tail: type? }?, returns: { head: {type}?, tail: type? }?, generics: {type}?) -> type,
copy: @checked (arg: type) -> type,
}
)BUILTIN_SRC";
std::string getTypeFunctionDefinitionSource()
{
return kBuiltinDefinitionTypesSrc;
std::string result = kBuiltinDefinitionTypeMethodSrc;
if (FFlag::LuauTypeFunOptional)
result += kBuiltinDefinitionTypesLibWithOptionalSrc;
else
result += kBuiltinDefinitionTypesLibSrc;
return result;
}
} // namespace Luau

View file

@ -330,9 +330,9 @@ Id toId(
return egraph.add(TOpaque{ty});
else if (get<FunctionType>(ty))
return egraph.add(TFunction{ty});
else if (ty == builtinTypes->classType)
else if (ty == builtinTypes->externType)
return egraph.add(TTopClass{});
else if (get<ClassType>(ty))
else if (get<ExternType>(ty))
return egraph.add(TClass{ty});
else if (get<AnyType>(ty))
return egraph.add(TAny{});
@ -752,7 +752,7 @@ TypeId fromId(
else if (node.get<TTopTable>())
return builtinTypes->tableType;
else if (node.get<TTopClass>())
return builtinTypes->classType;
return builtinTypes->externType;
else if (node.get<TBuffer>())
return builtinTypes->bufferType;
else if (auto opaque = node.get<TOpaque>())
@ -1007,7 +1007,7 @@ static std::string getNodeName(const StringCache& strings, const EType& node)
return "\xe2\x88\xa9";
else if (auto cls = node.get<TClass>())
{
const ClassType* ct = get<ClassType>(cls->value());
const ExternType* ct = get<ExternType>(cls->value());
LUAU_ASSERT(ct);
return ct->name;
}
@ -1177,12 +1177,12 @@ enum SubclassRelationship
static SubclassRelationship relateClasses(const TClass* leftClass, const TClass* rightClass)
{
const ClassType* leftClassType = Luau::get<ClassType>(leftClass->value());
const ClassType* rightClassType = Luau::get<ClassType>(rightClass->value());
const ExternType* leftExternType = Luau::get<ExternType>(leftClass->value());
const ExternType* rightExternType = Luau::get<ExternType>(rightClass->value());
if (isSubclass(leftClassType, rightClassType))
if (isSubclass(leftExternType, rightExternType))
return RightSuper;
else if (isSubclass(rightClassType, leftClassType))
else if (isSubclass(rightExternType, leftExternType))
return LeftSuper;
else
return Unrelated;

View file

@ -203,7 +203,7 @@ struct ErrorConverter
TypeId t = follow(e.table);
if (get<TableType>(t))
return "Key '" + e.key + "' not found in table '" + Luau::toString(t) + "'";
else if (get<ClassType>(t))
else if (get<ExternType>(t))
return "Key '" + e.key + "' not found in class '" + Luau::toString(t) + "'";
else
return "Type '" + Luau::toString(e.table) + "' does not have key '" + e.key + "'";
@ -371,7 +371,7 @@ struct ErrorConverter
std::string s = "Key '" + e.key + "' not found in ";
TypeId t = follow(e.table);
if (get<ClassType>(t))
if (get<ExternType>(t))
s += "class";
else
s += "table";
@ -402,8 +402,8 @@ struct ErrorConverter
std::optional<TypeId> metatable;
if (const MetatableType* mtType = get<MetatableType>(type))
metatable = mtType->metatable;
else if (const ClassType* classType = get<ClassType>(type))
metatable = classType->metatable;
else if (const ExternType* externType = get<ExternType>(type))
metatable = externType->metatable;
if (!metatable)
return std::nullopt;
@ -611,7 +611,7 @@ struct ErrorConverter
return ss;
}
std::string operator()(const DynamicPropertyLookupOnClassesUnsafe& e) const
std::string operator()(const DynamicPropertyLookupOnExternTypesUnsafe& e) const
{
return "Attempting a dynamic property access on type '" + Luau::toString(e.ty) + "' is unsafe and may cause exceptions at runtime";
}
@ -1149,7 +1149,7 @@ bool TypePackMismatch::operator==(const TypePackMismatch& rhs) const
return *wantedTp == *rhs.wantedTp && *givenTp == *rhs.givenTp;
}
bool DynamicPropertyLookupOnClassesUnsafe::operator==(const DynamicPropertyLookupOnClassesUnsafe& rhs) const
bool DynamicPropertyLookupOnExternTypesUnsafe::operator==(const DynamicPropertyLookupOnExternTypesUnsafe& rhs) const
{
return ty == rhs.ty;
}
@ -1391,7 +1391,7 @@ void copyError(T& e, TypeArena& destArena, CloneState& cloneState)
e.wantedTp = clone(e.wantedTp);
e.givenTp = clone(e.givenTp);
}
else if constexpr (std::is_same_v<T, DynamicPropertyLookupOnClassesUnsafe>)
else if constexpr (std::is_same_v<T, DynamicPropertyLookupOnExternTypesUnsafe>)
e.ty = clone(e.ty);
else if constexpr (std::is_same_v<T, UninhabitedTypeFunction>)
e.ty = clone(e.ty);

View file

@ -10,10 +10,6 @@
#include <string_view>
#include <utility>
LUAU_FASTFLAGVARIABLE(LuauExposeRequireByStringAutocomplete)
LUAU_FASTFLAGVARIABLE(LuauEscapeCharactersInRequireSuggestions)
LUAU_FASTFLAGVARIABLE(LuauHideImpossibleRequireSuggestions)
namespace Luau
{
@ -22,12 +18,9 @@ static std::optional<RequireSuggestions> processRequireSuggestions(std::optional
if (!suggestions)
return suggestions;
if (FFlag::LuauEscapeCharactersInRequireSuggestions)
for (RequireSuggestion& suggestion : *suggestions)
{
for (RequireSuggestion& suggestion : *suggestions)
{
suggestion.fullPath = escape(suggestion.fullPath);
}
suggestion.fullPath = escape(suggestion.fullPath);
}
return suggestions;
@ -112,13 +105,11 @@ static RequireSuggestions makeSuggestionsFromNode(std::unique_ptr<RequireNode> n
continue;
std::string pathComponent = child->getPathComponent();
if (FFlag::LuauHideImpossibleRequireSuggestions)
{
// If path component contains a slash, it cannot be required by string.
// There's no point suggesting it.
if (pathComponent.find('/') != std::string::npos)
continue;
}
// If path component contains a slash, it cannot be required by string.
// There's no point suggesting it.
if (pathComponent.find('/') != std::string::npos)
continue;
RequireSuggestion suggestion;
suggestion.label = isPartialPath || path.back() == '/' ? child->getLabel() : "/" + child->getLabel();
@ -163,9 +154,6 @@ std::optional<RequireSuggestions> RequireSuggester::getRequireSuggestions(const
std::optional<RequireSuggestions> FileResolver::getRequireSuggestions(const ModuleName& requirer, const std::optional<std::string>& path) const
{
if (!FFlag::LuauExposeRequireByStringAutocomplete)
return std::nullopt;
return requireSuggester ? requireSuggester->getRequireSuggestions(requirer, path) : std::nullopt;
}

View file

@ -27,10 +27,7 @@
LUAU_FASTINT(LuauTypeInferRecursionLimit);
LUAU_FASTINT(LuauTypeInferIterationLimit);
LUAU_FASTINT(LuauTarjanChildLimit)
LUAU_FASTFLAG(LuauAutocompleteRefactorsForIncrementalAutocomplete)
LUAU_FASTFLAGVARIABLE(LuauMixedModeDefFinderTraversesTypeOf)
LUAU_FASTFLAGVARIABLE(LuauCloneIncrementalModule)
LUAU_FASTFLAGVARIABLE(DebugLogFragmentsFromAutocomplete)
LUAU_FASTFLAGVARIABLE(LuauBetterCursorInCommentDetection)
LUAU_FASTFLAGVARIABLE(LuauAllFreeTypesHaveScopes)
@ -43,6 +40,7 @@ LUAU_FASTFLAGVARIABLE(LuauBetterScopeSelection)
LUAU_FASTFLAGVARIABLE(LuauBlockDiffFragmentSelection)
LUAU_FASTFLAGVARIABLE(LuauFragmentAcMemoryLeak)
LUAU_FASTFLAGVARIABLE(LuauGlobalVariableModuleIsolation)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
namespace
{
@ -97,7 +95,11 @@ Location getFunctionDeclarationExtents(AstExprFunction* exprFn, AstExpr* exprNam
{
auto fnBegin = exprFn->location.begin;
auto fnEnd = exprFn->location.end;
if (auto returnAnnot = exprFn->returnAnnotation)
if (auto returnAnnot = exprFn->returnAnnotation; FFlag::LuauStoreReturnTypesAsPackOnAst && returnAnnot)
{
fnEnd = returnAnnot->location.end;
}
else if (auto returnAnnot = exprFn->returnAnnotation_DEPRECATED; !FFlag::LuauStoreReturnTypesAsPackOnAst && returnAnnot)
{
if (returnAnnot->tailType)
fnEnd = returnAnnot->tailType->location.end;
@ -542,6 +544,11 @@ struct UsageFinder : public AstVisitor
return true;
}
bool visit(AstTypePack* node) override
{
return FFlag::LuauStoreReturnTypesAsPackOnAst;
}
bool visit(AstStatTypeAlias* alias) override
{
declaredAliases.insert(std::string(alias->name.value));
@ -728,7 +735,7 @@ struct MixedModeIncrementalTCDefFinder : public AstVisitor
// requires that we find the local/global `m` and place it in the environment.
// The default behaviour here is to return false, and have individual visitors override
// the specific behaviour they need.
return FFlag::LuauMixedModeDefFinderTraversesTypeOf;
return true;
}
bool visit(AstStatTypeAlias* alias) override
@ -1218,31 +1225,6 @@ ModulePtr cloneModule(CloneState& cloneState, const ModulePtr& source, std::uniq
return incremental;
}
ModulePtr copyModule(const ModulePtr& result, std::unique_ptr<Allocator> alloc)
{
ModulePtr incrementalModule = std::make_shared<Module>();
incrementalModule->name = result->name;
incrementalModule->humanReadableName = "Incremental$" + result->humanReadableName;
incrementalModule->internalTypes.owningModule = incrementalModule.get();
incrementalModule->interfaceTypes.owningModule = incrementalModule.get();
incrementalModule->allocator = std::move(alloc);
// Don't need to keep this alive (it's already on the source module)
copyModuleVec(incrementalModule->scopes, result->scopes);
copyModuleMap(incrementalModule->astTypes, result->astTypes);
copyModuleMap(incrementalModule->astTypePacks, result->astTypePacks);
copyModuleMap(incrementalModule->astExpectedTypes, result->astExpectedTypes);
// Don't need to clone astOriginalCallTypes
copyModuleMap(incrementalModule->astOverloadResolvedTypes, result->astOverloadResolvedTypes);
// Don't need to clone astForInNextTypes
copyModuleMap(incrementalModule->astForInNextTypes, result->astForInNextTypes);
// Don't need to clone astResolvedTypes
// Don't need to clone astResolvedTypePacks
// Don't need to clone upperBoundContributors
copyModuleMap(incrementalModule->astScopes, result->astScopes);
// Don't need to clone declared Globals;
return incrementalModule;
}
void mixedModeCompatibility(
const ScopePtr& bottomScopeStale,
const ScopePtr& myFakeScope,
@ -1306,10 +1288,8 @@ FragmentTypeCheckResult typecheckFragmentHelper_DEPRECATED(
ModulePtr incrementalModule = nullptr;
if (FFlag::LuauAllFreeTypesHaveScopes)
incrementalModule = cloneModule(cloneState, stale, std::move(astAllocator), freshChildOfNearestScope.get());
else if (FFlag::LuauCloneIncrementalModule)
incrementalModule = cloneModule_DEPRECATED(cloneState, stale, std::move(astAllocator));
else
incrementalModule = copyModule(stale, std::move(astAllocator));
incrementalModule = cloneModule_DEPRECATED(cloneState, stale, std::move(astAllocator));
reportWaypoint(reporter, FragmentAutocompleteWaypoint::CloneModuleEnd);
incrementalModule->checkedInNewSolver = true;
@ -1363,44 +1343,27 @@ FragmentTypeCheckResult typecheckFragmentHelper_DEPRECATED(
};
reportWaypoint(reporter, FragmentAutocompleteWaypoint::CloneAndSquashScopeStart);
if (FFlag::LuauCloneIncrementalModule)
{
incrementalModule->scopes.emplace_back(root->location, freshChildOfNearestScope);
cg.rootScope = freshChildOfNearestScope.get();
incrementalModule->scopes.emplace_back(root->location, freshChildOfNearestScope);
cg.rootScope = freshChildOfNearestScope.get();
if (FFlag::LuauAllFreeTypesHaveScopes)
cloneAndSquashScopes(
cloneState, closestScope.get(), stale, NotNull{&incrementalModule->internalTypes}, NotNull{&dfg}, root, freshChildOfNearestScope.get()
);
else
cloneAndSquashScopes_DEPRECATED(
cloneState, closestScope.get(), stale, NotNull{&incrementalModule->internalTypes}, NotNull{&dfg}, root, freshChildOfNearestScope.get()
);
reportWaypoint(reporter, FragmentAutocompleteWaypoint::CloneAndSquashScopeEnd);
cg.visitFragmentRoot(freshChildOfNearestScope, root);
if (FFlag::LuauPersistConstraintGenerationScopes)
{
for (auto p : cg.scopes)
incrementalModule->scopes.emplace_back(std::move(p));
}
}
if (FFlag::LuauAllFreeTypesHaveScopes)
cloneAndSquashScopes(
cloneState, closestScope.get(), stale, NotNull{&incrementalModule->internalTypes}, NotNull{&dfg}, root, freshChildOfNearestScope.get()
);
else
cloneAndSquashScopes_DEPRECATED(
cloneState, closestScope.get(), stale, NotNull{&incrementalModule->internalTypes}, NotNull{&dfg}, root, freshChildOfNearestScope.get()
);
reportWaypoint(reporter, FragmentAutocompleteWaypoint::CloneAndSquashScopeEnd);
cg.visitFragmentRoot(freshChildOfNearestScope, root);
if (FFlag::LuauPersistConstraintGenerationScopes)
{
// Any additions to the scope must occur in a fresh scope
cg.rootScope = stale->getModuleScope().get();
incrementalModule->scopes.emplace_back(root->location, freshChildOfNearestScope);
mixedModeCompatibility(closestScope, freshChildOfNearestScope, stale, NotNull{&dfg}, root);
// closest Scope -> children = { ...., freshChildOfNearestScope}
// We need to trim nearestChild from the scope hierarchy
closestScope->children.emplace_back(freshChildOfNearestScope.get());
cg.visitFragmentRoot(freshChildOfNearestScope, root);
// Trim nearestChild from the closestScope
Scope* back = closestScope->children.back().get();
LUAU_ASSERT(back == freshChildOfNearestScope.get());
closestScope->children.pop_back();
for (auto p : cg.scopes)
incrementalModule->scopes.emplace_back(std::move(p));
}
reportWaypoint(reporter, FragmentAutocompleteWaypoint::ConstraintSolverStart);
if (FFlag::LuauAllFreeTypesHaveScopes)
@ -1706,7 +1669,6 @@ FragmentAutocompleteResult fragmentAutocomplete(
IFragmentAutocompleteReporter* reporter
)
{
LUAU_ASSERT(FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete);
LUAU_TIMETRACE_SCOPE("Luau::fragmentAutocomplete", "FragmentAutocomplete");
LUAU_TIMETRACE_ARGUMENT("name", moduleName.c_str());

View file

@ -40,6 +40,7 @@ LUAU_FASTFLAG(LuauInferInNoCheckMode)
LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3)
LUAU_FASTFLAG(LuauSolverV2)
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauRethrowKnownExceptions, false)
LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson)
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJsonFile)
LUAU_FASTFLAGVARIABLE(DebugLuauForbidInternalTypes)
@ -128,9 +129,9 @@ static void generateDocumentationSymbols(TypeId ty, const std::string& rootName)
prop.documentationSymbol = rootName + "." + name;
}
}
else if (ClassType* ctv = getMutable<ClassType>(ty))
else if (ExternType* etv = getMutable<ExternType>(ty))
{
for (auto& [name, prop] : ctv->props)
for (auto& [name, prop] : etv->props)
{
prop.documentationSymbol = rootName + "." + name;
}
@ -1304,7 +1305,7 @@ ModulePtr check(
struct InternalTypeFinder : TypeOnceVisitor
{
bool visit(TypeId, const ClassType&) override
bool visit(TypeId, const ExternType&) override
{
return false;
}
@ -1419,30 +1420,59 @@ ModulePtr check(
requireCycles
};
cg.visitModuleRoot(sourceModule.root);
result->errors = std::move(cg.errors);
// FIXME: Delete this flag when clipping FFlag::DebugLuauGreedyGeneralization.
//
// This optional<> only exists so that we can run one constructor when the flag
// is set, and another when it is unset.
std::optional<ConstraintSolver> cs;
ConstraintSolver cs{
NotNull{&normalizer},
NotNull{simplifier.get()},
NotNull{&typeFunctionRuntime},
NotNull(cg.rootScope),
borrowConstraints(cg.constraints),
NotNull{&cg.scopeToFunction},
result->name,
moduleResolver,
requireCycles,
logger.get(),
NotNull{&dfg},
limits
};
if (FFlag::DebugLuauGreedyGeneralization)
{
ConstraintSet constraintSet = cg.run(sourceModule.root);
result->errors = std::move(constraintSet.errors);
cs.emplace(
NotNull{&normalizer},
NotNull{simplifier.get()},
NotNull{&typeFunctionRuntime},
result->name,
moduleResolver,
requireCycles,
logger.get(),
NotNull{&dfg},
limits,
std::move(constraintSet)
);
}
else
{
cg.visitModuleRoot(sourceModule.root);
result->errors = std::move(cg.errors);
cs.emplace(
NotNull{&normalizer},
NotNull{simplifier.get()},
NotNull{&typeFunctionRuntime},
NotNull(cg.rootScope),
borrowConstraints(cg.constraints),
NotNull{&cg.scopeToFunction},
result->name,
moduleResolver,
requireCycles,
logger.get(),
NotNull{&dfg},
limits
);
}
LUAU_ASSERT(bool(cs));
if (options.randomizeConstraintResolutionSeed)
cs.randomize(*options.randomizeConstraintResolutionSeed);
cs->randomize(*options.randomizeConstraintResolutionSeed);
try
{
cs.run();
cs->run();
}
catch (const TimeLimitError&)
{
@ -1462,12 +1492,12 @@ ModulePtr check(
printf("%s\n", output.c_str());
}
for (TypeError& e : cs.errors)
for (TypeError& e : cs->errors)
result->errors.emplace_back(std::move(e));
result->scopes = std::move(cg.scopes);
result->type = sourceModule.type;
result->upperBoundContributors = std::move(cs.upperBoundContributors);
result->upperBoundContributors = std::move(cs->upperBoundContributors);
if (result->timeout || result->cancelled)
{

View file

@ -15,7 +15,6 @@
#include "Luau/VisitType.h"
LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
LUAU_FASTFLAG(LuauAutocompleteRefactorsForIncrementalAutocomplete)
LUAU_FASTFLAGVARIABLE(LuauNonReentrantGeneralization2)
@ -549,7 +548,7 @@ struct FreeTypeSearcher : TypeVisitor
traverse(*prop.readTy);
else
{
LUAU_ASSERT(prop.isShared() || FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete);
LUAU_ASSERT(prop.isShared());
Polarity p = polarity;
polarity = Polarity::Mixed;
@ -605,7 +604,7 @@ struct FreeTypeSearcher : TypeVisitor
return false;
}
bool visit(TypeId, const ClassType&) override
bool visit(TypeId, const ExternType&) override
{
return false;
}
@ -897,7 +896,7 @@ struct TypeCacher : TypeOnceVisitor
return false;
}
bool visit(TypeId ty, const ClassType&) override
bool visit(TypeId ty, const ExternType&) override
{
cache(ty);
return false;
@ -1404,7 +1403,10 @@ std::optional<TypeId> generalize(
}
for (TypeId unsealedTableTy : fts.unsealedTables)
sealTable(scope, unsealedTableTy);
{
if (!generalizationTarget || unsealedTableTy == *generalizationTarget)
sealTable(scope, unsealedTableTy);
}
for (const auto& [freePackId, params] : fts.typePacks)
{
@ -1464,29 +1466,93 @@ std::optional<TypeId> generalize(
struct GenericCounter : TypeVisitor
{
struct CounterState
{
size_t count = 0;
Polarity polarity = Polarity::None;
};
NotNull<DenseHashSet<TypeId>> cachedTypes;
DenseHashMap<TypeId, size_t> generics{nullptr};
DenseHashMap<TypePackId, size_t> genericPacks{nullptr};
DenseHashMap<TypeId, CounterState> generics{nullptr};
DenseHashMap<TypePackId, CounterState> genericPacks{nullptr};
Polarity polarity = Polarity::Positive;
explicit GenericCounter(NotNull<DenseHashSet<TypeId>> cachedTypes)
: cachedTypes(cachedTypes)
{
}
bool visit(TypeId ty, const FunctionType& ft) override
{
if (ty->persistent)
return false;
polarity = invert(polarity);
traverse(ft.argTypes);
polarity = invert(polarity);
traverse(ft.retTypes);
return false;
}
bool visit(TypeId ty, const TableType& tt) override
{
if (ty->persistent)
return false;
const Polarity previous = polarity;
for (const auto& [_name, prop] : tt.props)
{
if (prop.isReadOnly())
traverse(*prop.readTy);
else
{
LUAU_ASSERT(prop.isShared());
polarity = Polarity::Mixed;
traverse(prop.type());
polarity = previous;
}
}
if (tt.indexer)
{
polarity = Polarity::Mixed;
traverse(tt.indexer->indexType);
traverse(tt.indexer->indexResultType);
polarity = previous;
}
return false;
}
bool visit(TypeId ty, const ExternType&) override
{
return false;
}
bool visit(TypeId ty, const GenericType&) override
{
size_t* count = generics.find(ty);
if (count)
++*count;
auto state = generics.find(ty);
if (state)
{
++state->count;
state->polarity |= polarity;
}
return false;
}
bool visit(TypePackId tp, const GenericTypePack&) override
{
size_t* count = genericPacks.find(tp);
if (count)
++*count;
auto state = genericPacks.find(tp);
if (state)
{
++state->count;
state->polarity |= polarity;
}
return false;
}
@ -1513,66 +1579,78 @@ void pruneUnnecessaryGenerics(
if (!functionTy)
return;
// Types (and packs) to be removed from the generics list
DenseHashSet<TypeId> clipTypes{nullptr};
DenseHashSet<TypePackId> clipTypePacks{nullptr};
// If a generic has no explicit name and is only referred to in one place in
// the function's signature, it can be replaced with unknown.
GenericCounter counter{cachedTypes};
for (TypeId generic : functionTy->generics)
{
generic = follow(generic);
auto g = get<GenericType>(generic);
LUAU_ASSERT(g);
if (!g)
clipTypes.insert(generic);
else if (!g->explicitName)
counter.generics[generic] = 0;
if (g && !g->explicitName)
counter.generics[generic] = {};
}
for (TypePackId genericPack : functionTy->genericPacks)
// It is sometimes the case that a pack in the generic list will become a
// pack that (transitively) has a generic tail. If it does, we need to add
// that generic tail to the generic pack list.
for (size_t i = 0; i < functionTy->genericPacks.size(); ++i)
{
auto g = get<GenericTypePack>(genericPack);
if (!g)
clipTypePacks.insert(genericPack);
else if (!g->explicitName)
counter.genericPacks[genericPack] = 0;
TypePackId genericPack = follow(functionTy->genericPacks[i]);
TypePackId tail = getTail(genericPack);
if (tail != genericPack)
functionTy->genericPacks.push_back(tail);
if (auto g = get<GenericTypePack>(tail); g && !g->explicitName)
counter.genericPacks[genericPack] = {};
}
counter.traverse(ty);
for (const auto& [generic, count] : counter.generics)
for (const auto& [generic, state] : counter.generics)
{
if (count == 1)
{
if (state.count == 1 && state.polarity != Polarity::Mixed)
emplaceType<BoundType>(asMutable(generic), builtinTypes->unknownType);
clipTypes.insert(generic);
}
}
// Remove duplicates and types that aren't actually generics.
DenseHashSet<TypeId> seen{nullptr};
auto it = std::remove_if(
functionTy->generics.begin(),
functionTy->generics.end(),
[&](TypeId ty)
{
return clipTypes.contains(ty);
ty = follow(ty);
if (seen.contains(ty))
return true;
seen.insert(ty);
return !get<GenericType>(ty);
}
);
functionTy->generics.erase(it, functionTy->generics.end());
for (const auto& [genericPack, count] : counter.genericPacks)
for (const auto& [genericPack, state] : counter.genericPacks)
{
if (count == 1)
{
if (state.count == 1)
emplaceTypePack<BoundTypePack>(asMutable(genericPack), builtinTypes->unknownTypePack);
clipTypePacks.insert(genericPack);
}
}
DenseHashSet<TypePackId> seen2{nullptr};
auto it2 = std::remove_if(
functionTy->genericPacks.begin(),
functionTy->genericPacks.end(),
[&](TypePackId tp)
{
return clipTypePacks.contains(tp);
tp = follow(tp);
if (seen2.contains(tp))
return true;
seen2.insert(tp);
return !get<GenericTypePack>(tp);
}
);

View file

@ -95,16 +95,16 @@ struct InferPolarity : TypeVisitor
// types.
for (TypeId generic : ft.generics)
{
generic = follow(generic);
const auto gen = get<GenericType>(generic);
LUAU_ASSERT(gen);
if (subsumes(scope, gen->scope))
if (gen && subsumes(scope, gen->scope))
types[generic] = Polarity::None;
}
for (TypePackId genericPack : ft.genericPacks)
{
genericPack = follow(genericPack);
const auto gen = get<GenericTypePack>(genericPack);
LUAU_ASSERT(gen);
if (subsumes(scope, gen->scope))
if (gen && subsumes(scope, gen->scope))
packs[genericPack] = Polarity::None;
}
@ -118,7 +118,7 @@ struct InferPolarity : TypeVisitor
return false;
}
bool visit(TypeId, const ClassType&) override
bool visit(TypeId, const ExternType&) override
{
return false;
}

View file

@ -11,7 +11,6 @@
#include <algorithm>
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
namespace Luau
{
@ -50,7 +49,7 @@ bool Instantiation::ignoreChildren(TypeId ty)
{
if (log->getMutable<FunctionType>(ty))
return true;
else if (get<ClassType>(ty))
else if (get<ExternType>(ty))
return true;
else
return false;
@ -120,7 +119,7 @@ bool ReplaceGenerics::ignoreChildren(TypeId ty)
// whenever we quantify, so the vectors overlap if and only if they are equal.
return (!generics.empty() || !genericPacks.empty()) && (ftv->generics == generics) && (ftv->genericPacks == genericPacks);
}
else if (get<ClassType>(ty))
else if (get<ExternType>(ty))
return true;
else
{
@ -164,7 +163,7 @@ TypeId ReplaceGenerics::clean(TypeId ty)
}
else
{
return FFlag::LuauFreeTypesMustHaveBounds ? arena->freshType(builtinTypes, scope, level) : addType(FreeType{scope, level});
return arena->freshType(builtinTypes, scope, level);
}
}

View file

@ -6,7 +6,7 @@ namespace Luau
bool Instantiation2::ignoreChildren(TypeId ty)
{
if (get<ClassType>(ty))
if (get<ExternType>(ty))
return true;
if (auto ftv = get<FunctionType>(ty))

View file

@ -193,8 +193,8 @@ static void errorToString(std::ostream& stream, const T& err)
stream << "NormalizationTooComplex { }";
else if constexpr (std::is_same_v<T, TypePackMismatch>)
stream << "TypePackMismatch { wanted = '" + toString(err.wantedTp) + "', given = '" + toString(err.givenTp) + "' }";
else if constexpr (std::is_same_v<T, DynamicPropertyLookupOnClassesUnsafe>)
stream << "DynamicPropertyLookupOnClassesUnsafe { " << toString(err.ty) << " }";
else if constexpr (std::is_same_v<T, DynamicPropertyLookupOnExternTypesUnsafe>)
stream << "DynamicPropertyLookupOnExternTypesUnsafe { " << toString(err.ty) << " }";
else if constexpr (std::is_same_v<T, UninhabitedTypeFunction>)
stream << "UninhabitedTypeFunction { " << toString(err.ty) << " }";
else if constexpr (std::is_same_v<T, ExplicitFunctionAnnotationRecommended>)

View file

@ -20,6 +20,7 @@ LUAU_FASTFLAG(LuauAttribute)
LUAU_FASTFLAGVARIABLE(LintRedundantNativeAttribute)
LUAU_FASTFLAG(LuauDeprecatedAttribute)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
namespace Luau
{
@ -908,6 +909,11 @@ private:
return true;
}
bool visit(AstTypePack* node) override
{
return FFlag::LuauStoreReturnTypesAsPackOnAst;
}
bool visit(AstTypeReference* node) override
{
if (!node->prefix)
@ -1970,6 +1976,11 @@ private:
return true;
}
bool visit(AstTypePack* node) override
{
return FFlag::LuauStoreReturnTypesAsPackOnAst;
}
bool visit(AstTypeTable* node) override
{
if (FFlag::LuauSolverV2)
@ -2372,9 +2383,9 @@ private:
void check(AstExprIndexName* node, TypeId ty)
{
if (const ClassType* cty = get<ClassType>(ty))
if (const ExternType* cty = get<ExternType>(ty))
{
const Property* prop = lookupClassProp(cty, node->index.value);
const Property* prop = lookupExternTypeProp(cty, node->index.value);
if (prop && prop->deprecated)
report(node->location, *prop, cty->name.c_str(), node->index.value);

View file

@ -22,10 +22,10 @@
LUAU_FASTFLAG(DebugLuauMagicTypes)
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
LUAU_FASTFLAGVARIABLE(LuauNonStrictVisitorImprovements)
LUAU_FASTFLAGVARIABLE(LuauNewNonStrictWarnOnUnknownGlobals)
LUAU_FASTFLAGVARIABLE(LuauNewNonStrictVisitTypes)
LUAU_FASTFLAGVARIABLE(LuauNewNonStrictVisitTypes2)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
namespace Luau
{
@ -217,7 +217,7 @@ struct NonStrictTypeChecker
return *fst;
else if (auto ftp = get<FreeTypePack>(pack))
{
TypeId result = FFlag::LuauFreeTypesMustHaveBounds ? arena->freshType(builtinTypes, ftp->scope) : arena->addType(FreeType{ftp->scope});
TypeId result = arena->freshType(builtinTypes, ftp->scope);
TypePackId freeTail = arena->addTypePack(FreeTypePack{ftp->scope});
TypePack* resultPack = emplaceTypePack<TypePack>(asMutable(pack));
@ -311,7 +311,7 @@ struct NonStrictTypeChecker
return visit(s);
else if (auto s = stat->as<AstStatDeclareGlobal>())
return visit(s);
else if (auto s = stat->as<AstStatDeclareClass>())
else if (auto s = stat->as<AstStatDeclareExternType>())
return visit(s);
else if (auto s = stat->as<AstStatError>())
return visit(s);
@ -340,11 +340,8 @@ struct NonStrictTypeChecker
{
ctx.remove(dfg->getDef(local));
if (FFlag::LuauNewNonStrictVisitTypes)
{
if (local->annotation)
visit(local->annotation);
}
if (FFlag::LuauNewNonStrictVisitTypes2)
visit(local->annotation);
}
}
else
@ -430,9 +427,8 @@ struct NonStrictTypeChecker
NonStrictContext visit(AstStatFor* forStatement)
{
if (FFlag::LuauNewNonStrictVisitTypes)
if (forStatement->var->annotation)
visit(forStatement->var->annotation);
if (FFlag::LuauNewNonStrictVisitTypes2)
visit(forStatement->var->annotation);
if (FFlag::LuauNonStrictVisitorImprovements)
{
@ -453,13 +449,10 @@ struct NonStrictTypeChecker
NonStrictContext visit(AstStatForIn* forInStatement)
{
if (FFlag::LuauNewNonStrictVisitTypes)
if (FFlag::LuauNewNonStrictVisitTypes2)
{
for (auto var : forInStatement->vars)
{
if (var->annotation)
visit(var->annotation);
}
visit(var->annotation);
}
if (FFlag::LuauNonStrictVisitorImprovements)
@ -510,7 +503,7 @@ struct NonStrictTypeChecker
NonStrictContext visit(AstStatTypeAlias* typeAlias)
{
if (FFlag::LuauNewNonStrictVisitTypes)
if (FFlag::LuauNewNonStrictVisitTypes2)
{
visitGenerics(typeAlias->generics, typeAlias->genericPacks);
visit(typeAlias->type);
@ -526,7 +519,7 @@ struct NonStrictTypeChecker
NonStrictContext visit(AstStatDeclareFunction* declFn)
{
if (FFlag::LuauNewNonStrictVisitTypes)
if (FFlag::LuauNewNonStrictVisitTypes2)
{
visitGenerics(declFn->generics, declFn->genericPacks);
visit(declFn->params);
@ -538,15 +531,15 @@ struct NonStrictTypeChecker
NonStrictContext visit(AstStatDeclareGlobal* declGlobal)
{
if (FFlag::LuauNewNonStrictVisitTypes)
if (FFlag::LuauNewNonStrictVisitTypes2)
visit(declGlobal->type);
return {};
}
NonStrictContext visit(AstStatDeclareClass* declClass)
NonStrictContext visit(AstStatDeclareExternType* declClass)
{
if (FFlag::LuauNewNonStrictVisitTypes)
if (FFlag::LuauNewNonStrictVisitTypes2)
{
if (declClass->indexer)
{
@ -822,19 +815,21 @@ struct NonStrictTypeChecker
}
remainder.remove(dfg->getDef(local));
if (FFlag::LuauNewNonStrictVisitTypes)
{
if (local->annotation)
visit(local->annotation);
}
if (FFlag::LuauNewNonStrictVisitTypes2)
visit(local->annotation);
}
if (FFlag::LuauNewNonStrictVisitTypes)
if (FFlag::LuauNewNonStrictVisitTypes2)
{
visitGenerics(exprFn->generics, exprFn->genericPacks);
if (exprFn->returnAnnotation)
visit(*exprFn->returnAnnotation);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
visit(exprFn->returnAnnotation);
else
{
if (exprFn->returnAnnotation_DEPRECATED)
visit(*exprFn->returnAnnotation_DEPRECATED);
}
if (exprFn->varargAnnotation)
visit(exprFn->varargAnnotation);
@ -880,7 +875,7 @@ struct NonStrictTypeChecker
NonStrictContext visit(AstExprTypeAssertion* typeAssertion)
{
if (FFlag::LuauNewNonStrictVisitTypes)
if (FFlag::LuauNewNonStrictVisitTypes2)
visit(typeAssertion->annotation);
if (FFlag::LuauNonStrictVisitorImprovements)
@ -921,7 +916,11 @@ struct NonStrictTypeChecker
void visit(AstType* ty)
{
LUAU_ASSERT(FFlag::LuauNewNonStrictVisitTypes);
LUAU_ASSERT(FFlag::LuauNewNonStrictVisitTypes2);
// If this node is `nullptr`, early exit.
if (!ty)
return;
if (auto t = ty->as<AstTypeReference>())
return visit(t);
@ -1130,7 +1129,11 @@ struct NonStrictTypeChecker
void visit(AstTypePack* pack)
{
LUAU_ASSERT(FFlag::LuauNewNonStrictVisitTypes);
LUAU_ASSERT(FFlag::LuauNewNonStrictVisitTypes2);
// If there is no pack node, early exit.
if (!pack)
return;
if (auto p = pack->as<AstTypePackExplicit>())
return visit(p);

View file

@ -249,23 +249,23 @@ bool isSubtype(const NormalizedStringType& subStr, const NormalizedStringType& s
return true;
}
void NormalizedClassType::pushPair(TypeId ty, TypeIds negations)
void NormalizedExternType::pushPair(TypeId ty, TypeIds negations)
{
auto result = classes.insert(std::make_pair(ty, std::move(negations)));
auto result = externTypes.insert(std::make_pair(ty, std::move(negations)));
if (result.second)
ordering.push_back(ty);
LUAU_ASSERT(ordering.size() == classes.size());
LUAU_ASSERT(ordering.size() == externTypes.size());
}
void NormalizedClassType::resetToNever()
void NormalizedExternType::resetToNever()
{
ordering.clear();
classes.clear();
externTypes.clear();
}
bool NormalizedClassType::isNever() const
bool NormalizedExternType::isNever() const
{
return classes.empty();
return externTypes.empty();
}
void NormalizedFunctionType::resetToTop()
@ -307,14 +307,14 @@ bool NormalizedType::isUnknown() const
strings.isString() && isThread(threads) && isBuffer(buffers);
// Check is class
bool isTopClass = false;
for (auto [t, disj] : classes.classes)
bool isTopExternType = false;
for (const auto& [t, disj] : externTypes.externTypes)
{
if (auto ct = get<ClassType>(t))
if (auto ct = get<ExternType>(t))
{
if (ct->name == "class" && disj.empty())
{
isTopClass = true;
isTopExternType = true;
break;
}
}
@ -330,24 +330,24 @@ bool NormalizedType::isUnknown() const
}
}
// any = unknown or error ==> we need to make sure we have all the unknown components, but not errors
return get<NeverType>(errors) && hasAllPrimitives && isTopClass && isTopTable && functions.isTop;
return get<NeverType>(errors) && hasAllPrimitives && isTopExternType && isTopTable && functions.isTop;
}
bool NormalizedType::isExactlyNumber() const
{
return hasNumbers() && !hasTops() && !hasBooleans() && !hasClasses() && !hasErrors() && !hasNils() && !hasStrings() && !hasThreads() &&
return hasNumbers() && !hasTops() && !hasBooleans() && !hasExternTypes() && !hasErrors() && !hasNils() && !hasStrings() && !hasThreads() &&
!hasBuffers() && !hasTables() && !hasFunctions() && !hasTyvars();
}
bool NormalizedType::isSubtypeOfString() const
{
return hasStrings() && !hasTops() && !hasBooleans() && !hasClasses() && !hasErrors() && !hasNils() && !hasNumbers() && !hasThreads() &&
return hasStrings() && !hasTops() && !hasBooleans() && !hasExternTypes() && !hasErrors() && !hasNils() && !hasNumbers() && !hasThreads() &&
!hasBuffers() && !hasTables() && !hasFunctions() && !hasTyvars();
}
bool NormalizedType::isSubtypeOfBooleans() const
{
return hasBooleans() && !hasTops() && !hasClasses() && !hasErrors() && !hasNils() && !hasNumbers() && !hasStrings() && !hasThreads() &&
return hasBooleans() && !hasTops() && !hasExternTypes() && !hasErrors() && !hasNils() && !hasNumbers() && !hasStrings() && !hasThreads() &&
!hasBuffers() && !hasTables() && !hasFunctions() && !hasTyvars();
}
@ -380,9 +380,9 @@ bool NormalizedType::hasBooleans() const
return !get<NeverType>(booleans);
}
bool NormalizedType::hasClasses() const
bool NormalizedType::hasExternTypes() const
{
return !classes.isNever();
return !externTypes.isNever();
}
bool NormalizedType::hasErrors() const
@ -440,7 +440,7 @@ bool NormalizedType::isFalsy() const
hasAFalse = !bs->value;
}
return (hasAFalse || hasNils()) && (!hasTops() && !hasClasses() && !hasErrors() && !hasNumbers() && !hasStrings() && !hasThreads() &&
return (hasAFalse || hasNils()) && (!hasTops() && !hasExternTypes() && !hasErrors() && !hasNumbers() && !hasStrings() && !hasThreads() &&
!hasBuffers() && !hasTables() && !hasFunctions() && !hasTyvars());
}
@ -452,7 +452,7 @@ bool NormalizedType::isTruthy() const
static bool isShallowInhabited(const NormalizedType& norm)
{
// This test is just a shallow check, for example it returns `true` for `{ p : never }`
return !get<NeverType>(norm.tops) || !get<NeverType>(norm.booleans) || !norm.classes.isNever() || !get<NeverType>(norm.errors) ||
return !get<NeverType>(norm.tops) || !get<NeverType>(norm.booleans) || !norm.externTypes.isNever() || !get<NeverType>(norm.errors) ||
!get<NeverType>(norm.nils) || !get<NeverType>(norm.numbers) || !norm.strings.isNever() || !get<NeverType>(norm.threads) ||
!get<NeverType>(norm.buffers) || !norm.functions.isNever() || !norm.tables.empty() || !norm.tyvars.empty();
}
@ -471,7 +471,7 @@ NormalizationResult Normalizer::isInhabited(const NormalizedType* norm, Set<Type
return NormalizationResult::HitLimits;
if (!get<NeverType>(norm->tops) || !get<NeverType>(norm->booleans) || !get<NeverType>(norm->errors) || !get<NeverType>(norm->nils) ||
!get<NeverType>(norm->numbers) || !get<NeverType>(norm->threads) || !get<NeverType>(norm->buffers) || !norm->classes.isNever() ||
!get<NeverType>(norm->numbers) || !get<NeverType>(norm->threads) || !get<NeverType>(norm->buffers) || !norm->externTypes.isNever() ||
!norm->strings.isNever() || !norm->functions.isNever())
return NormalizationResult::True;
@ -619,13 +619,13 @@ static int tyvarIndex(TypeId ty)
return 0;
}
static bool isTop(NotNull<BuiltinTypes> builtinTypes, const NormalizedClassType& classes)
static bool isTop(NotNull<BuiltinTypes> builtinTypes, const NormalizedExternType& externTypes)
{
if (classes.classes.size() != 1)
if (externTypes.externTypes.size() != 1)
return false;
auto first = classes.classes.begin();
if (first->first != builtinTypes->classType)
auto first = externTypes.externTypes.begin();
if (first->first != builtinTypes->externType)
return false;
if (!first->second.empty())
@ -634,11 +634,11 @@ static bool isTop(NotNull<BuiltinTypes> builtinTypes, const NormalizedClassType&
return true;
}
static void resetToTop(NotNull<BuiltinTypes> builtinTypes, NormalizedClassType& classes)
static void resetToTop(NotNull<BuiltinTypes> builtinTypes, NormalizedExternType& externTypes)
{
classes.ordering.clear();
classes.classes.clear();
classes.pushPair(builtinTypes->classType, TypeIds{});
externTypes.ordering.clear();
externTypes.externTypes.clear();
externTypes.pushPair(builtinTypes->externType, TypeIds{});
}
#ifdef LUAU_ASSERTENABLED
@ -762,50 +762,50 @@ static bool areNormalizedTables(const TypeIds& tys)
return true;
}
static bool areNormalizedClasses(const NormalizedClassType& tys)
static bool areNormalizedExternTypes(const NormalizedExternType& tys)
{
for (const auto& [ty, negations] : tys.classes)
for (const auto& [ty, negations] : tys.externTypes)
{
const ClassType* ctv = get<ClassType>(ty);
if (!ctv)
const ExternType* etv = get<ExternType>(ty);
if (!etv)
{
return false;
}
for (TypeId negation : negations)
{
const ClassType* nctv = get<ClassType>(negation);
const ExternType* nctv = get<ExternType>(negation);
if (!nctv)
{
return false;
}
if (!isSubclass(nctv, ctv))
if (!isSubclass(nctv, etv))
{
return false;
}
}
for (const auto& [otherTy, otherNegations] : tys.classes)
for (const auto& [otherTy, otherNegations] : tys.externTypes)
{
if (otherTy == ty)
continue;
const ClassType* octv = get<ClassType>(otherTy);
const ExternType* octv = get<ExternType>(otherTy);
if (!octv)
{
return false;
}
if (isSubclass(ctv, octv))
if (isSubclass(etv, octv))
{
auto iss = [ctv](TypeId t)
auto iss = [etv](TypeId t)
{
const ClassType* c = get<ClassType>(t);
const ExternType* c = get<ExternType>(t);
if (!c)
return false;
return isSubclass(ctv, c);
return isSubclass(etv, c);
};
if (!std::any_of(otherNegations.begin(), otherNegations.end(), iss))
@ -847,7 +847,7 @@ static void assertInvariant(const NormalizedType& norm)
LUAU_ASSERT(isNormalizedTop(norm.tops));
LUAU_ASSERT(isNormalizedBoolean(norm.booleans));
LUAU_ASSERT(areNormalizedClasses(norm.classes));
LUAU_ASSERT(areNormalizedExternTypes(norm.externTypes));
LUAU_ASSERT(isNormalizedError(norm.errors));
LUAU_ASSERT(isNormalizedNil(norm.nils));
LUAU_ASSERT(isNormalizedNumber(norm.numbers));
@ -988,7 +988,7 @@ void Normalizer::clearNormal(NormalizedType& norm)
{
norm.tops = builtinTypes->neverType;
norm.booleans = builtinTypes->neverType;
norm.classes.resetToNever();
norm.externTypes.resetToNever();
norm.errors = builtinTypes->neverType;
norm.nils = builtinTypes->neverType;
norm.numbers = builtinTypes->neverType;
@ -1138,17 +1138,17 @@ TypeId Normalizer::unionOfBools(TypeId here, TypeId there)
return builtinTypes->booleanType;
}
void Normalizer::unionClassesWithClass(TypeIds& heres, TypeId there)
void Normalizer::unionExternTypesWithExternType(TypeIds& heres, TypeId there)
{
if (heres.count(there))
return;
const ClassType* tctv = get<ClassType>(there);
const ExternType* tctv = get<ExternType>(there);
for (auto it = heres.begin(); it != heres.end();)
{
TypeId here = *it;
const ClassType* hctv = get<ClassType>(here);
const ExternType* hctv = get<ExternType>(here);
if (isSubclass(tctv, hctv))
return;
else if (isSubclass(hctv, tctv))
@ -1160,16 +1160,16 @@ void Normalizer::unionClassesWithClass(TypeIds& heres, TypeId there)
heres.insert(there);
}
void Normalizer::unionClasses(TypeIds& heres, const TypeIds& theres)
void Normalizer::unionExternTypes(TypeIds& heres, const TypeIds& theres)
{
for (TypeId there : theres)
unionClassesWithClass(heres, there);
unionExternTypesWithExternType(heres, there);
}
static bool isSubclass(TypeId test, TypeId parent)
{
const ClassType* testCtv = get<ClassType>(test);
const ClassType* parentCtv = get<ClassType>(parent);
const ExternType* testCtv = get<ExternType>(test);
const ExternType* parentCtv = get<ExternType>(parent);
LUAU_ASSERT(testCtv);
LUAU_ASSERT(parentCtv);
@ -1177,12 +1177,12 @@ static bool isSubclass(TypeId test, TypeId parent)
return isSubclass(testCtv, parentCtv);
}
void Normalizer::unionClassesWithClass(NormalizedClassType& heres, TypeId there)
void Normalizer::unionExternTypesWithExternType(NormalizedExternType& heres, TypeId there)
{
for (auto it = heres.ordering.begin(); it != heres.ordering.end();)
{
TypeId hereTy = *it;
TypeIds& hereNegations = heres.classes.at(hereTy);
TypeIds& hereNegations = heres.externTypes.at(hereTy);
// If the incoming class is a subclass of another class in the map, we
// must ensure that it is negated by one of the negations in the same
@ -1204,7 +1204,7 @@ void Normalizer::unionClassesWithClass(NormalizedClassType& heres, TypeId there)
}
// If the incoming class is a superclass of one of the
// negations, then the negation no longer applies and must be
// removed. This is also true if they are equal. Since classes
// removed. This is also true if they are equal. Since extern types
// are, at this time, entirely persistent (we do not clone
// them), a pointer identity check is sufficient.
else if (isSubclass(hereNegation, there))
@ -1231,7 +1231,7 @@ void Normalizer::unionClassesWithClass(NormalizedClassType& heres, TypeId there)
{
TypeIds negations = std::move(hereNegations);
it = heres.ordering.erase(it);
heres.classes.erase(hereTy);
heres.externTypes.erase(hereTy);
heres.pushPair(there, std::move(negations));
return;
@ -1248,10 +1248,10 @@ void Normalizer::unionClassesWithClass(NormalizedClassType& heres, TypeId there)
heres.pushPair(there, TypeIds{});
}
void Normalizer::unionClasses(NormalizedClassType& heres, const NormalizedClassType& theres)
void Normalizer::unionExternTypes(NormalizedExternType& heres, const NormalizedExternType& theres)
{
// This method bears much similarity with unionClassesWithClass, but is
// solving a more general problem. In unionClassesWithClass, we are dealing
// This method bears much similarity with unionExternTypesWithExternType, but is
// solving a more general problem. In unionExternTypesWithExternType, we are dealing
// with a singular positive type. Since it's one type, we can use early
// returns as control flow. Since it's guaranteed to be positive, we do not
// have negations to worry about combining. The two aspects combine to make
@ -1260,9 +1260,9 @@ void Normalizer::unionClasses(NormalizedClassType& heres, const NormalizedClassT
for (const TypeId thereTy : theres.ordering)
{
const TypeIds& thereNegations = theres.classes.at(thereTy);
const TypeIds& thereNegations = theres.externTypes.at(thereTy);
// If it happens that there are _no_ classes in the current map, or the
// If it happens that there are _no_ extern types in the current map, or the
// incoming class is completely unrelated to any class in the current
// map, we must insert the incoming pair as-is.
bool insert = true;
@ -1270,7 +1270,7 @@ void Normalizer::unionClasses(NormalizedClassType& heres, const NormalizedClassT
for (auto it = heres.ordering.begin(); it != heres.ordering.end();)
{
TypeId hereTy = *it;
TypeIds& hereNegations = heres.classes.at(hereTy);
TypeIds& hereNegations = heres.externTypes.at(hereTy);
if (isSubclass(thereTy, hereTy))
{
@ -1294,7 +1294,7 @@ void Normalizer::unionClasses(NormalizedClassType& heres, const NormalizedClassT
// If the incoming class is a superclass of one of the
// negations, then the negation no longer applies and must
// be removed. This is also true if they are equal. Since
// classes are, at this time, entirely persistent (we do not
// extern types are, at this time, entirely persistent (we do not
// clone them), a pointer identity check is sufficient.
else if (isSubclass(hereNegateTy, thereTy))
{
@ -1319,17 +1319,17 @@ void Normalizer::unionClasses(NormalizedClassType& heres, const NormalizedClassT
else if (isSubclass(hereTy, thereTy))
{
TypeIds negations = std::move(hereNegations);
unionClasses(negations, thereNegations);
unionExternTypes(negations, thereNegations);
it = heres.ordering.erase(it);
heres.classes.erase(hereTy);
heres.externTypes.erase(hereTy);
heres.pushPair(thereTy, std::move(negations));
insert = false;
break;
}
else if (hereTy == thereTy)
{
unionClasses(hereNegations, thereNegations);
unionExternTypes(hereNegations, thereNegations);
insert = false;
break;
}
@ -1690,7 +1690,7 @@ NormalizationResult Normalizer::unionNormals(NormalizedType& here, const Normali
return NormalizationResult::HitLimits;
here.booleans = unionOfBools(here.booleans, there.booleans);
unionClasses(here.classes, there.classes);
unionExternTypes(here.externTypes, there.externTypes);
here.errors = (get<NeverType>(there.errors) ? here.errors : there.errors);
here.nils = (get<NeverType>(there.nils) ? here.nils : there.nils);
@ -1830,8 +1830,8 @@ NormalizationResult Normalizer::unionNormalWithTy(
unionFunctionsWithFunction(here.functions, there);
else if (get<TableType>(there) || get<MetatableType>(there))
unionTablesWithTable(here.tables, there);
else if (get<ClassType>(there))
unionClassesWithClass(here.classes, there);
else if (get<ExternType>(there))
unionExternTypesWithExternType(here.externTypes, there);
else if (get<ErrorType>(there))
here.errors = there;
else if (const PrimitiveType* ptv = get<PrimitiveType>(there))
@ -1944,29 +1944,29 @@ std::optional<NormalizedType> Normalizer::negateNormal(const NormalizedType& her
result.booleans = builtinTypes->trueType;
}
if (here.classes.isNever())
if (here.externTypes.isNever())
{
resetToTop(builtinTypes, result.classes);
resetToTop(builtinTypes, result.externTypes);
}
else if (isTop(builtinTypes, result.classes))
else if (isTop(builtinTypes, result.externTypes))
{
result.classes.resetToNever();
result.externTypes.resetToNever();
}
else
{
TypeIds rootNegations{};
for (const auto& [hereParent, hereNegations] : here.classes.classes)
for (const auto& [hereParent, hereNegations] : here.externTypes.externTypes)
{
if (hereParent != builtinTypes->classType)
if (hereParent != builtinTypes->externType)
rootNegations.insert(hereParent);
for (TypeId hereNegation : hereNegations)
unionClassesWithClass(result.classes, hereNegation);
unionExternTypesWithExternType(result.externTypes, hereNegation);
}
if (!rootNegations.empty())
result.classes.pushPair(builtinTypes->classType, rootNegations);
result.externTypes.pushPair(builtinTypes->externType, rootNegations);
}
result.nils = get<NeverType>(here.nils) ? builtinTypes->nilType : builtinTypes->neverType;
@ -2144,7 +2144,7 @@ TypeId Normalizer::intersectionOfBools(TypeId here, TypeId there)
return there;
}
void Normalizer::intersectClasses(NormalizedClassType& heres, const NormalizedClassType& theres)
void Normalizer::intersectExternTypes(NormalizedExternType& heres, const NormalizedExternType& theres)
{
if (theres.isNever())
{
@ -2178,12 +2178,12 @@ void Normalizer::intersectClasses(NormalizedClassType& heres, const NormalizedCl
// declare the result of the intersection operation to be never.
for (const TypeId thereTy : theres.ordering)
{
const TypeIds& thereNegations = theres.classes.at(thereTy);
const TypeIds& thereNegations = theres.externTypes.at(thereTy);
for (auto it = heres.ordering.begin(); it != heres.ordering.end();)
{
TypeId hereTy = *it;
TypeIds& hereNegations = heres.classes.at(hereTy);
TypeIds& hereNegations = heres.externTypes.at(hereTy);
if (isSubclass(thereTy, hereTy))
{
@ -2206,10 +2206,10 @@ void Normalizer::intersectClasses(NormalizedClassType& heres, const NormalizedCl
}
}
unionClasses(negations, thereNegations);
unionExternTypes(negations, thereNegations);
it = heres.ordering.erase(it);
heres.classes.erase(hereTy);
heres.externTypes.erase(hereTy);
heres.pushPair(thereTy, std::move(negations));
break;
}
@ -2234,15 +2234,15 @@ void Normalizer::intersectClasses(NormalizedClassType& heres, const NormalizedCl
{
if (isSubclass(hereTy, *nIt))
{
// eg SomeClass & (class & ~SomeClass)
// or SomeClass & (class & ~ParentClass)
heres.classes.erase(hereTy);
// eg SomeExternType & (class & ~SomeExternType)
// or SomeExternType & (class & ~ParentExternType)
heres.externTypes.erase(hereTy);
it = heres.ordering.erase(it);
erasedHere = true;
break;
}
// eg SomeClass & (class & ~Unrelated)
// eg SomeExternType & (class & ~Unrelated)
if (!isSubclass(*nIt, hereTy))
nIt = negations.erase(nIt);
else
@ -2251,30 +2251,30 @@ void Normalizer::intersectClasses(NormalizedClassType& heres, const NormalizedCl
if (!erasedHere)
{
unionClasses(hereNegations, negations);
unionExternTypes(hereNegations, negations);
++it;
}
}
else if (hereTy == thereTy)
{
unionClasses(hereNegations, thereNegations);
unionExternTypes(hereNegations, thereNegations);
break;
}
else
{
it = heres.ordering.erase(it);
heres.classes.erase(hereTy);
heres.externTypes.erase(hereTy);
}
}
}
}
void Normalizer::intersectClassesWithClass(NormalizedClassType& heres, TypeId there)
void Normalizer::intersectExternTypesWithExternType(NormalizedExternType& heres, TypeId there)
{
for (auto it = heres.ordering.begin(); it != heres.ordering.end();)
{
TypeId hereTy = *it;
const TypeIds& hereNegations = heres.classes.at(hereTy);
const TypeIds& hereNegations = heres.externTypes.at(hereTy);
// If the incoming class _is_ the current class, we skip it. Maybe
// another entry will have a different story. We check for this first
@ -2319,7 +2319,7 @@ void Normalizer::intersectClassesWithClass(NormalizedClassType& heres, TypeId th
}
it = heres.ordering.erase(it);
heres.classes.erase(hereTy);
heres.externTypes.erase(hereTy);
if (!emptyIntersectWithNegation)
heres.pushPair(there, std::move(negations));
break;
@ -2335,7 +2335,7 @@ void Normalizer::intersectClassesWithClass(NormalizedClassType& heres, TypeId th
else
{
it = heres.ordering.erase(it);
heres.classes.erase(hereTy);
heres.externTypes.erase(hereTy);
}
}
}
@ -3083,7 +3083,7 @@ NormalizationResult Normalizer::intersectNormals(NormalizedType& here, const Nor
here.booleans = intersectionOfBools(here.booleans, there.booleans);
intersectClasses(here.classes, there.classes);
intersectExternTypes(here.externTypes, there.externTypes);
here.errors = (get<NeverType>(there.errors) ? there.errors : here.errors);
here.nils = (get<NeverType>(there.nils) ? there.nils : here.nils);
here.numbers = (get<NeverType>(there.numbers) ? there.numbers : here.numbers);
@ -3205,12 +3205,12 @@ NormalizationResult Normalizer::intersectNormalWithTy(
intersectTablesWithTable(tables, there, seenTablePropPairs, seenSetTypes);
here.tables = std::move(tables);
}
else if (get<ClassType>(there))
else if (get<ExternType>(there))
{
NormalizedClassType nct = std::move(here.classes);
NormalizedExternType nct = std::move(here.externTypes);
clearNormal(here);
intersectClassesWithClass(nct, there);
here.classes = std::move(nct);
intersectExternTypesWithExternType(nct, there);
here.externTypes = std::move(nct);
}
else if (get<ErrorType>(there))
{
@ -3274,7 +3274,7 @@ NormalizationResult Normalizer::intersectNormalWithTy(
subtractPrimitive(here, ntv->ty);
else if (const SingletonType* stv = get<SingletonType>(t))
subtractSingleton(here, follow(ntv->ty));
else if (get<ClassType>(t))
else if (get<ExternType>(t))
{
NormalizationResult res = intersectNormalWithNegationTy(t, here);
if (shouldEarlyExit(res))
@ -3334,7 +3334,7 @@ NormalizationResult Normalizer::intersectNormalWithTy(
}
else if (get<NeverType>(there))
{
here.classes.resetToNever();
here.externTypes.resetToNever();
}
else if (get<NoRefineType>(there))
{
@ -3403,18 +3403,18 @@ TypeId Normalizer::typeFromNormal(const NormalizedType& norm)
if (!get<NeverType>(norm.booleans))
result.push_back(norm.booleans);
if (isTop(builtinTypes, norm.classes))
if (isTop(builtinTypes, norm.externTypes))
{
result.push_back(builtinTypes->classType);
result.push_back(builtinTypes->externType);
}
else if (!norm.classes.isNever())
else if (!norm.externTypes.isNever())
{
std::vector<TypeId> parts;
parts.reserve(norm.classes.classes.size());
parts.reserve(norm.externTypes.externTypes.size());
for (const TypeId normTy : norm.classes.ordering)
for (const TypeId normTy : norm.externTypes.ordering)
{
const TypeIds& normNegations = norm.classes.classes.at(normTy);
const TypeIds& normNegations = norm.externTypes.externTypes.at(normTy);
if (normNegations.empty())
{

View file

@ -4,6 +4,8 @@
#include "Luau/Ast.h"
#include "Luau/Module.h"
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
namespace Luau
{
@ -65,6 +67,12 @@ struct RequireTracer : AstVisitor
return true;
}
bool visit(AstTypePack* node) override
{
// allow resolving require inside `typeof` annotations
return FFlag::LuauStoreReturnTypesAsPackOnAst;
}
AstExpr* getDependent_DEPRECATED(AstExpr* node)
{
if (AstExprLocal* expr = node->as<AstExprLocal>())

View file

@ -6,6 +6,7 @@
#include "Luau/DenseHash.h"
#include "Luau/RecursionCounter.h"
#include "Luau/Set.h"
#include "Luau/Type.h"
#include "Luau/TypeArena.h"
#include "Luau/TypePairHash.h"
#include "Luau/TypeUtils.h"
@ -17,6 +18,7 @@ LUAU_FASTFLAG(LuauSolverV2)
LUAU_DYNAMIC_FASTINTVARIABLE(LuauSimplificationComplexityLimit, 8)
LUAU_FASTFLAGVARIABLE(LuauSimplificationRecheckAssumption)
LUAU_FASTFLAGVARIABLE(LuauOptimizeFalsyAndTruthyIntersect)
LUAU_FASTFLAGVARIABLE(LuauSimplificationTableExternType)
namespace Luau
{
@ -316,12 +318,14 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
{
if (get<AnyType>(right))
return Relation::Subset;
else if (get<UnknownType>(right))
if (get<UnknownType>(right))
return Relation::Coincident;
else if (get<ErrorType>(right))
if (get<ErrorType>(right))
return Relation::Disjoint;
else
return Relation::Superset;
return Relation::Superset;
}
if (get<UnknownType>(right))
@ -331,8 +335,8 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
{
if (get<AnyType>(right))
return Relation::Coincident;
else
return Relation::Superset;
return Relation::Superset;
}
if (get<AnyType>(right))
@ -356,7 +360,7 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
// * FunctionType
// * TableType
// * MetatableType
// * ClassType
// * ExternType
// * UnionType
// * IntersectionType
// * NegationType
@ -364,26 +368,33 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
if (isTypeVariable(left) || isTypeVariable(right))
return Relation::Intersects;
if (FFlag::LuauSimplificationTableExternType)
{
// if either type is a type function, we cannot know if they'll be related.
if (get<TypeFunctionInstanceType>(left) || get<TypeFunctionInstanceType>(right))
return Relation::Intersects;
}
if (get<ErrorType>(left))
{
if (get<ErrorType>(right))
return Relation::Coincident;
else if (get<AnyType>(right))
return Relation::Subset;
else
return Relation::Disjoint;
return Relation::Disjoint;
}
if (get<ErrorType>(right))
else if (get<ErrorType>(right))
return flip(relate(right, left, seen));
if (get<NeverType>(left))
{
if (get<NeverType>(right))
return Relation::Coincident;
else
return Relation::Subset;
return Relation::Subset;
}
if (get<NeverType>(right))
else if (get<NeverType>(right))
return flip(relate(right, left, seen));
if (auto ut = get<IntersectionType>(left))
@ -447,52 +458,54 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
{
if (lp->type == rp->type)
return Relation::Coincident;
else
return Relation::Disjoint;
return Relation::Disjoint;
}
if (auto rs = get<SingletonType>(right))
{
if (lp->type == PrimitiveType::String && rs->variant.get_if<StringSingleton>())
return Relation::Superset;
else if (lp->type == PrimitiveType::Boolean && rs->variant.get_if<BooleanSingleton>())
if (lp->type == PrimitiveType::Boolean && rs->variant.get_if<BooleanSingleton>())
return Relation::Superset;
else
return Relation::Disjoint;
return Relation::Disjoint;
}
if (lp->type == PrimitiveType::Function)
{
if (get<FunctionType>(right))
return Relation::Superset;
else
return Relation::Disjoint;
return Relation::Disjoint;
}
if (lp->type == PrimitiveType::Table)
{
if (get<TableType>(right))
return Relation::Superset;
else
return Relation::Disjoint;
return Relation::Disjoint;
}
if (get<FunctionType>(right) || get<TableType>(right) || get<MetatableType>(right) || get<ClassType>(right))
if (get<FunctionType>(right) || get<TableType>(right) || get<MetatableType>(right) || get<ExternType>(right))
return Relation::Disjoint;
}
if (auto ls = get<SingletonType>(left))
{
if (get<FunctionType>(right) || get<TableType>(right) || get<MetatableType>(right) || get<ClassType>(right))
if (get<FunctionType>(right) || get<TableType>(right) || get<MetatableType>(right) || get<ExternType>(right))
return Relation::Disjoint;
if (get<PrimitiveType>(right))
return flip(relate(right, left, seen));
if (auto rs = get<SingletonType>(right))
{
if (ls->variant == rs->variant)
return Relation::Coincident;
else
return Relation::Disjoint;
return Relation::Disjoint;
}
}
@ -502,11 +515,11 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
{
if (rp->type == PrimitiveType::Function)
return Relation::Subset;
else
return Relation::Disjoint;
return Relation::Disjoint;
}
else
return Relation::Intersects;
return Relation::Intersects;
}
if (auto lt = get<TableType>(left))
@ -515,10 +528,11 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
{
if (rp->type == PrimitiveType::Table)
return Relation::Subset;
else
return Relation::Disjoint;
return Relation::Disjoint;
}
else if (auto rt = get<TableType>(right))
if (auto rt = get<TableType>(right))
{
// TODO PROBABLY indexers and metatables.
if (1 == rt->props.size())
@ -538,29 +552,58 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
*/
if (lt->props.size() > 1 && r == Relation::Superset)
return Relation::Intersects;
else
return r;
return r;
}
else if (1 == lt->props.size())
if (1 == lt->props.size())
return flip(relate(right, left, seen));
else
return Relation::Intersects;
return Relation::Intersects;
}
if (FFlag::LuauSimplificationTableExternType)
{
if (auto re = get<ExternType>(right))
{
Relation overall = Relation::Coincident;
for (auto& [name, prop] : lt->props)
{
if (auto propInExternType = re->props.find(name); propInExternType != re->props.end())
{
Relation propRel = relate(prop.type(), propInExternType->second.type());
if (propRel == Relation::Disjoint)
return Relation::Disjoint;
if (propRel == Relation::Coincident)
continue;
overall = Relation::Intersects;
}
}
return overall;
}
}
// TODO metatables
return Relation::Disjoint;
}
if (auto ct = get<ClassType>(left))
if (auto ct = get<ExternType>(left))
{
if (auto rct = get<ClassType>(right))
if (auto rct = get<ExternType>(right))
{
if (isSubclass(ct, rct))
return Relation::Subset;
else if (isSubclass(rct, ct))
if (isSubclass(rct, ct))
return Relation::Superset;
else
return Relation::Disjoint;
return Relation::Disjoint;
}
return Relation::Disjoint;
@ -873,7 +916,7 @@ std::optional<TypeId> TypeSimplifier::basicIntersectWithTruthy(TypeId target) co
if (is<NeverType, ErrorType>(target))
return target;
if (is<FunctionType, TableType, MetatableType, ClassType>(target))
if (is<FunctionType, TableType, MetatableType, ExternType>(target))
return target;
if (auto pt = get<PrimitiveType>(target))
@ -909,7 +952,7 @@ std::optional<TypeId> TypeSimplifier::basicIntersectWithFalsy(TypeId target) con
if (is<UnknownType>(target))
return builtinTypes->falsyType;
if (is<FunctionType, TableType, MetatableType, ClassType>(target))
if (is<FunctionType, TableType, MetatableType, ExternType>(target))
return builtinTypes->neverType;
if (auto pt = get<PrimitiveType>(target))

View file

@ -136,9 +136,9 @@ static TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log)
clone.parts = a.parts;
return dest.addType(std::move(clone));
}
else if constexpr (std::is_same_v<T, ClassType>)
else if constexpr (std::is_same_v<T, ExternType>)
{
ClassType clone{a.name, a.props, a.parent, a.metatable, a.tags, a.userData, a.definitionModuleName, a.definitionLocation, a.indexer};
ExternType clone{a.name, a.props, a.parent, a.metatable, a.tags, a.userData, a.definitionModuleName, a.definitionLocation, a.indexer};
return dest.addType(std::move(clone));
}
else if constexpr (std::is_same_v<T, NegationType>)
@ -252,21 +252,21 @@ void Tarjan::visitChildren(TypeId ty, int index)
for (TypePackId a : tfit->packArguments)
visitChild(a);
}
else if (const ClassType* ctv = get<ClassType>(ty))
else if (const ExternType* etv = get<ExternType>(ty))
{
for (const auto& [name, prop] : ctv->props)
for (const auto& [name, prop] : etv->props)
visitChild(prop.type());
if (ctv->parent)
visitChild(*ctv->parent);
if (etv->parent)
visitChild(*etv->parent);
if (ctv->metatable)
visitChild(*ctv->metatable);
if (etv->metatable)
visitChild(*etv->metatable);
if (ctv->indexer)
if (etv->indexer)
{
visitChild(ctv->indexer->indexType);
visitChild(ctv->indexer->indexResultType);
visitChild(etv->indexer->indexType);
visitChild(etv->indexer->indexResultType);
}
}
else if (const NegationType* ntv = get<NegationType>(ty))
@ -838,21 +838,21 @@ void Substitution::replaceChildren(TypeId ty)
for (TypePackId& a : tfit->packArguments)
a = replace(a);
}
else if (ClassType* ctv = getMutable<ClassType>(ty))
else if (ExternType* etv = getMutable<ExternType>(ty))
{
for (auto& [name, prop] : ctv->props)
for (auto& [name, prop] : etv->props)
prop.setType(replace(prop.type()));
if (ctv->parent)
ctv->parent = replace(*ctv->parent);
if (etv->parent)
etv->parent = replace(*etv->parent);
if (ctv->metatable)
ctv->metatable = replace(*ctv->metatable);
if (etv->metatable)
etv->metatable = replace(*etv->metatable);
if (ctv->indexer)
if (etv->indexer)
{
ctv->indexer->indexType = replace(ctv->indexer->indexType);
ctv->indexer->indexResultType = replace(ctv->indexer->indexResultType);
etv->indexer->indexType = replace(etv->indexer->indexType);
etv->indexer->indexResultType = replace(etv->indexer->indexResultType);
}
}
else if (NegationType* ntv = getMutable<NegationType>(ty))

View file

@ -7,13 +7,11 @@
#include "Luau/Normalize.h"
#include "Luau/RecursionCounter.h"
#include "Luau/Scope.h"
#include "Luau/StringUtils.h"
#include "Luau/Substitution.h"
#include "Luau/ToString.h"
#include "Luau/TxnLog.h"
#include "Luau/Type.h"
#include "Luau/TypeArena.h"
#include "Luau/TypeCheckLimits.h"
#include "Luau/TypeFunction.h"
#include "Luau/TypePack.h"
#include "Luau/TypePath.h"
@ -33,7 +31,7 @@ struct VarianceFlipper
Subtyping::Variance* variance;
Subtyping::Variance oldValue;
VarianceFlipper(Subtyping::Variance* v)
explicit VarianceFlipper(Subtyping::Variance* v)
: variance(v)
, oldValue(*v)
{
@ -315,7 +313,7 @@ struct ApplyMappedGenerics : Substitution
bool ignoreChildren(TypeId ty) override
{
if (get<ClassType>(ty))
if (get<ExternType>(ty))
return true;
return ty->persistent;
@ -744,9 +742,9 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
result = isCovariantWith(env, p, scope);
else if (auto p = get2<MetatableType, TableType>(subTy, superTy))
result = isCovariantWith(env, p, scope);
else if (auto p = get2<ClassType, ClassType>(subTy, superTy))
else if (auto p = get2<ExternType, ExternType>(subTy, superTy))
result = isCovariantWith(env, p, scope);
else if (auto p = get2<ClassType, TableType>(subTy, superTy))
else if (auto p = get2<ExternType, TableType>(subTy, superTy))
result = isCovariantWith(env, subTy, p.first, superTy, p.second, scope);
else if (auto p = get2<TableType, PrimitiveType>(subTy, superTy))
result = isCovariantWith(env, p, scope);
@ -1336,7 +1334,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Type
}
// the top class type is not actually a primitive type, so the negation of
// any one of them includes the top class type.
else if (auto p = get2<ClassType, PrimitiveType>(subTy, negatedTy))
else if (auto p = get2<ExternType, PrimitiveType>(subTy, negatedTy))
result = {true};
else if (auto p = get<PrimitiveType>(negatedTy); p && is<TableType, MetatableType>(subTy))
result = {p->type != PrimitiveType::Table};
@ -1344,9 +1342,9 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Type
result = {p.second->type != PrimitiveType::Function};
else if (auto p = get2<SingletonType, SingletonType>(subTy, negatedTy))
result = {*p.first != *p.second};
else if (auto p = get2<ClassType, ClassType>(subTy, negatedTy))
else if (auto p = get2<ExternType, ExternType>(subTy, negatedTy))
result = SubtypingResult::negate(isCovariantWith(env, p.first, p.second, scope));
else if (get2<FunctionType, ClassType>(subTy, negatedTy))
else if (get2<FunctionType, ExternType>(subTy, negatedTy))
result = {true};
else if (is<ErrorType, FunctionType, TableType, MetatableType>(negatedTy))
iceReporter->ice("attempting to negate a non-testable type");
@ -1471,15 +1469,15 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Meta
}
}
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const ClassType* subClass, const ClassType* superClass, NotNull<Scope> scope)
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const ExternType* subExternType, const ExternType* superExternType, NotNull<Scope> scope)
{
return {isSubclass(subClass, superClass)};
return {isSubclass(subExternType, superExternType)};
}
SubtypingResult Subtyping::isCovariantWith(
SubtypingEnvironment& env,
TypeId subTy,
const ClassType* subClass,
const ExternType* subExternType,
TypeId superTy,
const TableType* superTable,
NotNull<Scope> scope
@ -1491,7 +1489,7 @@ SubtypingResult Subtyping::isCovariantWith(
for (const auto& [name, prop] : superTable->props)
{
if (auto classProp = lookupClassProp(subClass, name))
if (auto classProp = lookupExternTypeProp(subExternType, name))
{
result.andAlso(isCovariantWith(env, *classProp, prop, name, scope));
}
@ -1661,7 +1659,7 @@ SubtypingResult Subtyping::isCovariantWith(
SubtypingResult result = isCovariantWith(env, subNorm->tops, superNorm->tops, scope);
result.andAlso(isCovariantWith(env, subNorm->booleans, superNorm->booleans, scope));
result.andAlso(
isCovariantWith(env, subNorm->classes, superNorm->classes, scope).orElse(isCovariantWith(env, subNorm->classes, superNorm->tables, scope))
isCovariantWith(env, subNorm->externTypes, superNorm->externTypes, scope).orElse(isCovariantWith(env, subNorm->externTypes, superNorm->tables, scope))
);
result.andAlso(isCovariantWith(env, subNorm->errors, superNorm->errors, scope));
result.andAlso(isCovariantWith(env, subNorm->nils, superNorm->nils, scope));
@ -1678,24 +1676,24 @@ SubtypingResult Subtyping::isCovariantWith(
SubtypingResult Subtyping::isCovariantWith(
SubtypingEnvironment& env,
const NormalizedClassType& subClass,
const NormalizedClassType& superClass,
const NormalizedExternType& subExternType,
const NormalizedExternType& superExternType,
NotNull<Scope> scope
)
{
for (const auto& [subClassTy, _] : subClass.classes)
for (const auto& [subExternTypeTy, _] : subExternType.externTypes)
{
SubtypingResult result;
for (const auto& [superClassTy, superNegations] : superClass.classes)
for (const auto& [superExternTypeTy, superNegations] : superExternType.externTypes)
{
result.orElse(isCovariantWith(env, subClassTy, superClassTy, scope));
result.orElse(isCovariantWith(env, subExternTypeTy, superExternTypeTy, scope));
if (!result.isSubtype)
continue;
for (TypeId negation : superNegations)
{
result.andAlso(SubtypingResult::negate(isCovariantWith(env, subClassTy, negation, scope)));
result.andAlso(SubtypingResult::negate(isCovariantWith(env, subExternTypeTy, negation, scope)));
if (result.isSubtype)
break;
}
@ -1710,17 +1708,17 @@ SubtypingResult Subtyping::isCovariantWith(
SubtypingResult Subtyping::isCovariantWith(
SubtypingEnvironment& env,
const NormalizedClassType& subClass,
const NormalizedExternType& subExternType,
const TypeIds& superTables,
NotNull<Scope> scope
)
{
for (const auto& [subClassTy, _] : subClass.classes)
for (const auto& [subExternTypeTy, _] : subExternType.externTypes)
{
SubtypingResult result;
for (TypeId superTableTy : superTables)
result.orElse(isCovariantWith(env, subClassTy, superTableTy, scope));
result.orElse(isCovariantWith(env, subExternTypeTy, superTableTy, scope));
if (!result.isSubtype)
return result;

View file

@ -299,9 +299,9 @@ void StateDot::visitChildren(TypeId ty, int index)
finishNodeLabel(ty);
finishNode();
}
else if constexpr (std::is_same_v<T, ClassType>)
else if constexpr (std::is_same_v<T, ExternType>)
{
formatAppend(result, "ClassType %s", t.name.c_str());
formatAppend(result, "ExternType %s", t.name.c_str());
finishNodeLabel(ty);
finishNode();

View file

@ -19,6 +19,8 @@
#include <stdexcept>
#include <string>
LUAU_FASTFLAGVARIABLE(LuauEnableDenseTableAlias)
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAGVARIABLE(LuauSyntheticErrors)
LUAU_FASTFLAGVARIABLE(LuauStringPartLengthLimit)
@ -121,7 +123,7 @@ struct FindCyclicTypes final : TypeVisitor
return true;
}
bool visit(TypeId ty, const ClassType&) override
bool visit(TypeId ty, const ExternType&) override
{
return false;
}
@ -720,7 +722,13 @@ struct TypeStringifier
if (ttv.boundTo)
return stringify(*ttv.boundTo);
if (!state.exhaustive)
bool showName = !state.exhaustive;
if (FFlag::LuauEnableDenseTableAlias)
{
// if hide table alias expansions are enabled and there is a name found for the table, use it
showName = !state.exhaustive || state.opts.hideTableAliasExpansions;
}
if (showName)
{
if (ttv.name)
{
@ -743,6 +751,10 @@ struct TypeStringifier
stringify(ttv.instantiatedTypeParams, ttv.instantiatedTypePackParams);
return;
}
}
if (!state.exhaustive)
{
if (ttv.syntheticName)
{
state.result.invalid = true;
@ -881,9 +893,9 @@ struct TypeStringifier
state.emit(" }");
}
void operator()(TypeId, const ClassType& ctv)
void operator()(TypeId, const ExternType& etv)
{
state.emit(ctv.name);
state.emit(etv.name);
}
void operator()(TypeId, const AnyType&)

View file

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

View file

@ -12,9 +12,9 @@
LUAU_FASTFLAG(LuauStoreCSTData2)
LUAU_FASTFLAG(LuauAstTypeGroup3)
LUAU_FASTFLAG(LuauFixDoBlockEndLocation)
LUAU_FASTFLAG(LuauParseOptionalAsNode2)
LUAU_FASTFLAG(LuauFixFunctionWithAttributesStartLocation)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
namespace
{
@ -331,7 +331,7 @@ struct Printer_DEPRECATED
}
}
void visualizeTypePackAnnotation(const AstTypePack& annotation, bool forVarArg)
void visualizeTypePackAnnotation(const AstTypePack& annotation, bool forVarArg, bool unconditionallyParenthesize = true)
{
advance(annotation.location.begin);
if (const AstTypePackVariadic* variadicTp = annotation.as<AstTypePackVariadic>())
@ -349,7 +349,7 @@ struct Printer_DEPRECATED
else if (const AstTypePackExplicit* explicitTp = annotation.as<AstTypePackExplicit>())
{
LUAU_ASSERT(!forVarArg);
visualizeTypeList(explicitTp->typeList, true);
visualizeTypeList(explicitTp->typeList, unconditionallyParenthesize);
}
else
{
@ -705,8 +705,6 @@ struct Printer_DEPRECATED
writer.keyword("do");
for (const auto& s : block->body)
visualize(*s);
if (!FFlag::LuauFixDoBlockEndLocation)
writer.advance(block->location.end);
writeEnd(program.location);
}
else if (const auto& a = program.as<AstStatIf>())
@ -1065,12 +1063,15 @@ struct Printer_DEPRECATED
writer.symbol(")");
if (writeTypes && func.returnAnnotation)
if (writeTypes && (FFlag::LuauStoreReturnTypesAsPackOnAst ? func.returnAnnotation != nullptr : func.returnAnnotation_DEPRECATED.has_value()))
{
writer.symbol(":");
writer.space();
visualizeTypeList(*func.returnAnnotation, false);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
visualizeTypePackAnnotation(*func.returnAnnotation, false, false);
else
visualizeTypeList(*func.returnAnnotation_DEPRECATED, false);
}
visualizeBlock(*func.body);
@ -1174,7 +1175,10 @@ struct Printer_DEPRECATED
}
writer.symbol("->");
visualizeTypeList(a->returnTypes, true);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
visualizeTypePackAnnotation(*a->returnTypes, false);
else
visualizeTypeList(a->returnTypes_DEPRECATED, true);
}
else if (const auto& a = typeAnnotation.as<AstTypeTable>())
{
@ -1368,7 +1372,7 @@ struct Printer
}
}
void visualizeTypePackAnnotation(AstTypePack& annotation, bool forVarArg)
void visualizeTypePackAnnotation(AstTypePack& annotation, bool forVarArg, bool unconditionallyParenthesize = true)
{
advance(annotation.location.begin);
if (const AstTypePackVariadic* variadicTp = annotation.as<AstTypePackVariadic>())
@ -1390,10 +1394,10 @@ struct Printer
LUAU_ASSERT(!forVarArg);
if (const auto cstNode = lookupCstNode<CstTypePackExplicit>(explicitTp))
visualizeTypeList(
explicitTp->typeList, true, cstNode->openParenthesesPosition, cstNode->closeParenthesesPosition, cstNode->commaPositions
explicitTp->typeList, FFlag::LuauStoreReturnTypesAsPackOnAst ? cstNode->hasParentheses : true, cstNode->openParenthesesPosition, cstNode->closeParenthesesPosition, cstNode->commaPositions
);
else
visualizeTypeList(explicitTp->typeList, true);
visualizeTypeList(explicitTp->typeList, unconditionallyParenthesize);
}
else
{
@ -2316,8 +2320,6 @@ struct Printer
{
const auto cstNode = lookupCstNode<CstExprFunction>(&func);
// TODO(CLI-139347): need to handle return type (incl. parentheses of return type)
if (func.generics.size > 0 || func.genericPacks.size > 0)
{
CommaSeparatorInserter comma(writer, cstNode ? cstNode->genericsCommaPositions.begin() : nullptr);
@ -2383,14 +2385,23 @@ struct Printer
advanceBefore(func.argLocation->end, 1);
writer.symbol(")");
if (writeTypes && func.returnAnnotation)
if (writeTypes && FFlag::LuauStoreReturnTypesAsPackOnAst ? func.returnAnnotation != nullptr : func.returnAnnotation_DEPRECATED.has_value())
{
if (cstNode)
advance(cstNode->returnSpecifierPosition);
writer.symbol(":");
writer.space();
visualizeTypeList(*func.returnAnnotation, false);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
if (!cstNode)
writer.space();
visualizeTypePackAnnotation(*func.returnAnnotation, false, false);
}
else
{
writer.space();
visualizeTypeList(*func.returnAnnotation_DEPRECATED, false);
}
}
visualizeBlock(*func.body);
@ -2573,7 +2584,10 @@ struct Printer
if (cstNode)
advance(cstNode->returnArrowPosition);
writer.symbol("->");
visualizeTypeList(a->returnTypes, true);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
visualizeTypePackAnnotation(*a->returnTypes, false);
else
visualizeTypeList(a->returnTypes_DEPRECATED, true);
}
else if (const auto& a = typeAnnotation.as<AstTypeTable>())
{

View file

@ -282,8 +282,8 @@ std::optional<TypeId> getMetatable(TypeId type, NotNull<BuiltinTypes> builtinTyp
if (const MetatableType* mtType = get<MetatableType>(type))
return mtType->metatable;
else if (const ClassType* classType = get<ClassType>(type))
return classType->metatable;
else if (const ExternType* externType = get<ExternType>(type))
return externType->metatable;
else if (isString(type))
{
auto ptv = get<PrimitiveType>(builtinTypes->stringType);
@ -346,10 +346,10 @@ std::optional<ModuleName> getDefinitionModuleName(TypeId type)
if (ftv->definition)
return ftv->definition->definitionModuleName;
}
else if (auto ctv = get<ClassType>(type))
else if (auto etv = get<ExternType>(type))
{
if (!ctv->definitionModuleName.empty())
return ctv->definitionModuleName;
if (!etv->definitionModuleName.empty())
return etv->definitionModuleName;
}
return std::nullopt;
@ -506,31 +506,6 @@ FreeType::FreeType(Scope* scope, TypeLevel level, TypeId lowerBound, TypeId uppe
{
}
// Old constructors
FreeType::FreeType(TypeLevel level)
: index(Unifiable::freshIndex())
, level(level)
, scope(nullptr)
{
LUAU_ASSERT(!FFlag::LuauFreeTypesMustHaveBounds);
}
FreeType::FreeType(Scope* scope)
: index(Unifiable::freshIndex())
, level{}
, scope(scope)
{
LUAU_ASSERT(!FFlag::LuauFreeTypesMustHaveBounds);
}
FreeType::FreeType(Scope* scope, TypeLevel level)
: index(Unifiable::freshIndex())
, level(level)
, scope(scope)
{
LUAU_ASSERT(!FFlag::LuauFreeTypesMustHaveBounds);
}
GenericType::GenericType()
: index(Unifiable::freshIndex())
, name("g" + std::to_string(index))
@ -1014,7 +989,7 @@ BuiltinTypes::BuiltinTypes()
, threadType(arena->addType(Type{PrimitiveType{PrimitiveType::Thread}, /*persistent*/ true}))
, bufferType(arena->addType(Type{PrimitiveType{PrimitiveType::Buffer}, /*persistent*/ true}))
, functionType(arena->addType(Type{PrimitiveType{PrimitiveType::Function}, /*persistent*/ true}))
, classType(arena->addType(Type{ClassType{"class", {}, std::nullopt, std::nullopt, {}, {}, {}, {}}, /*persistent*/ true}))
, externType(arena->addType(Type{ExternType{"class", {}, std::nullopt, std::nullopt, {}, {}, {}, {}}, /*persistent*/ true}))
, tableType(arena->addType(Type{PrimitiveType{PrimitiveType::Table}, /*persistent*/ true}))
, emptyTableType(arena->addType(Type{TableType{TableState::Sealed, TypeLevel{}, nullptr}, /*persistent*/ true}))
, trueType(arena->addType(Type{SingletonType{BooleanSingleton{true}}, /*persistent*/ true}))
@ -1026,6 +1001,7 @@ BuiltinTypes::BuiltinTypes()
, noRefineType(arena->addType(Type{NoRefineType{}, /*persistent*/ true}))
, falsyType(arena->addType(Type{UnionType{{falseType, nilType}}, /*persistent*/ true}))
, truthyType(arena->addType(Type{NegationType{falsyType}, /*persistent*/ true}))
, notNilType(arena->addType(Type{NegationType{nilType}, /*persistent*/ true}))
, optionalNumberType(arena->addType(Type{UnionType{{numberType, nilType}}, /*persistent*/ true}))
, optionalStringType(arena->addType(Type{UnionType{{stringType, nilType}}, /*persistent*/ true}))
, emptyTypePack(arena->addTypePack(TypePackVar{TypePack{{}}, /*persistent*/ true}))
@ -1104,9 +1080,9 @@ void persist(TypeId ty)
queue.push_back(ttv->indexer->indexResultType);
}
}
else if (auto ctv = get<ClassType>(t))
else if (auto etv= get<ExternType>(t))
{
for (const auto& [_name, prop] : ctv->props)
for (const auto& [_name, prop] : etv->props)
queue.push_back(prop.type());
}
else if (auto utv = get<UnionType>(t))
@ -1206,7 +1182,7 @@ std::optional<TypeLevel> getLevel(TypePackId tp)
return std::nullopt;
}
const Property* lookupClassProp(const ClassType* cls, const Name& name)
const Property* lookupExternTypeProp(const ExternType* cls, const Name& name)
{
while (cls)
{
@ -1215,7 +1191,7 @@ const Property* lookupClassProp(const ClassType* cls, const Name& name)
return &it->second;
if (cls->parent)
cls = get<ClassType>(*cls->parent);
cls = get<ExternType>(*cls->parent);
else
return nullptr;
@ -1225,7 +1201,7 @@ const Property* lookupClassProp(const ClassType* cls, const Name& name)
return nullptr;
}
bool isSubclass(const ClassType* cls, const ClassType* parent)
bool isSubclass(const ExternType* cls, const ExternType* parent)
{
while (cls)
{
@ -1234,7 +1210,7 @@ bool isSubclass(const ClassType* cls, const ClassType* parent)
else if (!cls->parent)
return false;
cls = get<ClassType>(*cls->parent);
cls = get<ExternType>(*cls->parent);
LUAU_ASSERT(cls);
}
@ -1303,8 +1279,8 @@ static Tags* getTags(TypeId ty)
return &ftv->tags;
else if (auto ttv = getMutable<TableType>(ty))
return &ttv->tags;
else if (auto ctv = getMutable<ClassType>(ty))
return &ctv->tags;
else if (auto etv = getMutable<ExternType>(ty))
return &etv->tags;
return nullptr;
}
@ -1334,19 +1310,19 @@ bool hasTag(TypeId ty, const std::string& tagName)
{
ty = follow(ty);
// We special case classes because getTags only returns a pointer to one vector of tags.
// But classes has multiple vector of tags, represented throughout the hierarchy.
if (auto ctv = get<ClassType>(ty))
// We special case extern types because getTags only returns a pointer to one vector of tags.
// But extern types has multiple vector of tags, represented throughout the hierarchy.
if (auto etv = get<ExternType>(ty))
{
while (ctv)
while (etv)
{
if (hasTag(ctv->tags, tagName))
if (hasTag(etv->tags, tagName))
return true;
else if (!ctv->parent)
else if (!etv->parent)
return false;
ctv = get<ClassType>(*ctv->parent);
LUAU_ASSERT(ctv);
etv = get<ExternType>(*etv->parent);
LUAU_ASSERT(etv);
}
}
else if (auto tags = getTags(ty))

View file

@ -50,33 +50,6 @@ TypeId TypeArena::freshType(NotNull<BuiltinTypes> builtins, Scope* scope, TypeLe
return allocated;
}
TypeId TypeArena::freshType_DEPRECATED(TypeLevel level)
{
TypeId allocated = types.allocate(FreeType{level});
asMutable(allocated)->owningArena = this;
return allocated;
}
TypeId TypeArena::freshType_DEPRECATED(Scope* scope)
{
TypeId allocated = types.allocate(FreeType{scope});
asMutable(allocated)->owningArena = this;
return allocated;
}
TypeId TypeArena::freshType_DEPRECATED(Scope* scope, TypeLevel level)
{
TypeId allocated = types.allocate(FreeType{scope, level});
asMutable(allocated)->owningArena = this;
return allocated;
}
TypePackId TypeArena::freshTypePack(Scope* scope, Polarity polarity)
{
TypePackId allocated = typePacks.allocate(FreeTypePack{scope, polarity});

View file

@ -14,6 +14,7 @@
#include <string>
LUAU_FASTFLAG(LuauStoreCSTData2)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
static char* allocateString(Luau::Allocator& allocator, std::string_view contents)
{
@ -219,21 +220,21 @@ public:
return Luau::visit(*this, mtv.table->ty);
}
AstType* operator()(const ClassType& ctv)
AstType* operator()(const ExternType& etv)
{
RecursionCounter counter(&count);
char* name = allocateString(*allocator, ctv.name);
char* name = allocateString(*allocator, etv.name);
if (!options.expandClassProps || hasSeen(&ctv) || count > 1)
if (!options.expandExternTypeProps || hasSeen(&etv) || count > 1)
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName{name}, std::nullopt, Location());
AstArray<AstTableProp> props;
props.size = ctv.props.size();
props.size = etv.props.size();
props.data = static_cast<AstTableProp*>(allocator->allocate(sizeof(AstTableProp) * props.size));
int idx = 0;
for (const auto& [propName, prop] : ctv.props)
for (const auto& [propName, prop] : etv.props)
{
char* name = allocateString(*allocator, propName);
@ -244,13 +245,13 @@ public:
}
AstTableIndexer* indexer = nullptr;
if (ctv.indexer)
if (etv.indexer)
{
RecursionCounter counter(&count);
indexer = allocator->alloc<AstTableIndexer>();
indexer->indexType = Luau::visit(*this, ctv.indexer->indexType->ty);
indexer->resultType = Luau::visit(*this, ctv.indexer->indexResultType->ty);
indexer->indexType = Luau::visit(*this, etv.indexer->indexType->ty);
indexer->resultType = Luau::visit(*this, etv.indexer->indexResultType->ty);
}
return allocator->alloc<AstTypeTable>(Location(), props, indexer);
@ -328,9 +329,19 @@ public:
if (retTail)
retTailAnnotation = rehydrate(*retTail);
return allocator->alloc<AstTypeFunction>(
Location(), generics, genericPacks, AstTypeList{argTypes, argTailAnnotation}, argNames, AstTypeList{returnTypes, retTailAnnotation}
);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
auto returnAnnotation = allocator->alloc<AstTypePackExplicit>(Location(), AstTypeList{returnTypes, retTailAnnotation});
return allocator->alloc<AstTypeFunction>(
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&)
{
@ -585,19 +596,40 @@ public:
visitLocal(arg);
}
if (!fn->returnAnnotation)
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
if (auto result = getScope(fn->body->location))
if (!fn->returnAnnotation)
{
TypePackId ret = result->returnType;
if (auto result = getScope(fn->body->location))
{
TypePackId ret = result->returnType;
AstTypePack* variadicAnnotation = nullptr;
const auto& [v, tail] = flatten(ret);
AstTypePack* variadicAnnotation = nullptr;
const auto& [v, tail] = flatten(ret);
if (tail)
variadicAnnotation = TypeRehydrationVisitor(allocator, &syntheticNames).rehydrate(*tail);
if (tail)
variadicAnnotation = TypeRehydrationVisitor(allocator, &syntheticNames).rehydrate(*tail);
fn->returnAnnotation = 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};
}
}
}

View file

@ -30,10 +30,11 @@
LUAU_FASTFLAG(DebugLuauMagicTypes)
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
LUAU_FASTFLAGVARIABLE(LuauImproveTypePathsInErrors)
LUAU_FASTFLAG(LuauUserTypeFunTypecheck)
LUAU_FASTFLAGVARIABLE(LuauTypeCheckerAcceptNumberConcats)
LUAU_FASTFLAGVARIABLE(LuauTypeCheckerStricterIndexCheck)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
namespace Luau
{
@ -661,7 +662,7 @@ void TypeChecker2::visit(AstStat* stat)
return visit(s);
else if (auto s = stat->as<AstStatDeclareGlobal>())
return visit(s);
else if (auto s = stat->as<AstStatDeclareClass>())
else if (auto s = stat->as<AstStatDeclareExternType>())
return visit(s);
else if (auto s = stat->as<AstStatError>())
return visit(s);
@ -1221,7 +1222,10 @@ void TypeChecker2::visit(AstStatDeclareFunction* stat)
{
visitGenerics(stat->generics, stat->genericPacks);
visit(stat->params);
visit(stat->retTypes);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
visit(stat->retTypes);
else
visit(stat->retTypes_DEPRECATED);
}
void TypeChecker2::visit(AstStatDeclareGlobal* stat)
@ -1229,9 +1233,9 @@ void TypeChecker2::visit(AstStatDeclareGlobal* stat)
visit(stat->type);
}
void TypeChecker2::visit(AstStatDeclareClass* stat)
void TypeChecker2::visit(AstStatDeclareExternType* stat)
{
for (const AstDeclaredClassProp& prop : stat->props)
for (const AstDeclaredExternTypeProperty& prop : stat->props)
visit(prop.ty);
}
@ -1675,12 +1679,12 @@ void TypeChecker2::visit(AstExprIndexExpr* indexExpr, ValueContext context)
{
return indexExprMetatableHelper(indexExpr, mt, exprType, indexType);
}
else if (auto cls = get<ClassType>(exprType))
else if (auto cls = get<ExternType>(exprType))
{
if (cls->indexer)
testIsSubtype(indexType, cls->indexer->indexType, indexExpr->index->location);
else
reportError(DynamicPropertyLookupOnClassesUnsafe{exprType}, indexExpr->location);
reportError(DynamicPropertyLookupOnExternTypesUnsafe{exprType}, indexExpr->location);
}
else if (get<UnionType>(exprType) && isOptional(exprType))
{
@ -1821,8 +1825,16 @@ void TypeChecker2::visit(AstExprFunction* fn)
visit(fn->body);
// we need to typecheck the return annotation itself, if it exists.
if (fn->returnAnnotation)
visit(*fn->returnAnnotation);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
if (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
@ -2036,7 +2048,7 @@ TypeId TypeChecker2::visit(AstExprBinary* expr, AstNode* overrideKey)
// If we're working with things that are not tables, the metatable comparisons above are a little excessive
// It's ok for one type to have a meta table and the other to not. In that case, we should fall back on
// checking if the intersection of the types is inhabited. If `typesHaveIntersection` failed due to limits,
// TODO: Maybe add more checks here (e.g. for functions, classes, etc)
// TODO: Maybe add more checks here (e.g. for functions, extern types, etc)
if (!(get<TableType>(leftType) || get<TableType>(rightType)))
if (!leftMt.has_value() || !rightMt.has_value())
matches = matches || typesHaveIntersection != NormalizationResult::False;
@ -2101,10 +2113,7 @@ TypeId TypeChecker2::visit(AstExprBinary* expr, AstNode* overrideKey)
}
else
{
expectedRets = module->internalTypes.addTypePack(
{FFlag::LuauFreeTypesMustHaveBounds ? module->internalTypes.freshType(builtinTypes, scope, TypeLevel{})
: module->internalTypes.freshType_DEPRECATED(scope, TypeLevel{})}
);
expectedRets = module->internalTypes.addTypePack({module->internalTypes.freshType(builtinTypes, scope, TypeLevel{})});
}
TypeId expectedTy = module->internalTypes.addType(FunctionType(expectedArgs, expectedRets));
@ -2367,8 +2376,7 @@ TypeId TypeChecker2::flattenPack(TypePackId pack)
return *fst;
else if (auto ftp = get<FreeTypePack>(pack))
{
TypeId result = FFlag::LuauFreeTypesMustHaveBounds ? module->internalTypes.freshType(builtinTypes, ftp->scope)
: module->internalTypes.addType(FreeType{ftp->scope});
TypeId result = module->internalTypes.freshType(builtinTypes, ftp->scope);
TypePackId freeTail = module->internalTypes.addTypePack(FreeTypePack{ftp->scope});
TypePack* resultPack = emplaceTypePack<TypePack>(asMutable(pack));
@ -2613,7 +2621,10 @@ void TypeChecker2::visit(AstTypeFunction* ty)
{
visitGenerics(ty->generics, ty->genericPacks);
visit(ty->argTypes);
visit(ty->returnTypes);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
visit(ty->returnTypes);
else
visit(ty->returnTypes_DEPRECATED);
}
void TypeChecker2::visit(AstTypeTypeof* ty)
@ -2937,7 +2948,7 @@ PropertyTypes TypeChecker2::lookupProp(
if (normValid)
{
for (const auto& [ty, _negations] : norm->classes.classes)
for (const auto& [ty, _negations] : norm->externTypes.externTypes)
{
fetch(ty);
@ -3032,10 +3043,10 @@ void TypeChecker2::checkIndexTypeFromType(
if (propTypes.foundOneProp())
reportError(MissingUnionProperty{tableTy, propTypes.missingProp, prop}, location);
// For class LValues, we don't want to report an extension error,
// because classes come into being with full knowledge of their
// because extern types come into being with full knowledge of their
// shape. We instead want to report the unknown property error of
// the `else` branch.
else if (context == ValueContext::LValue && !get<ClassType>(tableTy))
else if (context == ValueContext::LValue && !get<ExternType>(tableTy))
{
const auto lvPropTypes = lookupProp(norm.get(), prop, ValueContext::RValue, location, astIndexExprType, dummy);
if (lvPropTypes.foundOneProp() && lvPropTypes.noneMissingProp())
@ -3045,7 +3056,7 @@ void TypeChecker2::checkIndexTypeFromType(
else
reportError(CannotExtendTable{tableTy, CannotExtendTable::Property, prop}, location);
}
else if (context == ValueContext::RValue && !get<ClassType>(tableTy))
else if (context == ValueContext::RValue && !get<ExternType>(tableTy))
{
const auto rvPropTypes = lookupProp(norm.get(), prop, ValueContext::LValue, location, astIndexExprType, dummy);
if (rvPropTypes.foundOneProp() && rvPropTypes.noneMissingProp())
@ -3098,19 +3109,25 @@ PropertyType TypeChecker2::hasIndexTypeFromType(
return {NormalizationResult::True, {tt->indexer->indexResultType}};
}
// if we are in a conditional context, we treat the property as present and `unknown` because
// we may be _refining_ `tableTy` to include that property. we will want to revisit this a bit
// in the future once luau has support for exact tables since this only applies when inexact.
return {inConditional(typeContext) ? NormalizationResult::True : NormalizationResult::False, {builtinTypes->unknownType}};
if (FFlag::LuauTypeCheckerStricterIndexCheck)
{
return {NormalizationResult::False, {builtinTypes->unknownType}};
}
else
{
// if we are in a conditional context, we treat the property as present and `unknown` because
// we may be _refining_ `tableTy` to include that property. we will want to revisit this a bit
// in the future once luau has support for exact tables since this only applies when inexact.
return {inConditional(typeContext) ? NormalizationResult::True : NormalizationResult::False, {builtinTypes->unknownType}};
}
}
else if (const ClassType* cls = get<ClassType>(ty))
else if (const ExternType* cls = get<ExternType>(ty))
{
// If the property doesn't exist on the class, we consult the indexer
// We need to check if the type of the index expression foo (x[foo])
// is compatible with the indexer's indexType
// Construct the intersection and test inhabitedness!
if (auto property = lookupClassProp(cls, prop))
if (auto property = lookupExternTypeProp(cls, prop))
return {NormalizationResult::True, context == ValueContext::LValue ? property->writeTy : property->readTy};
if (cls->indexer)
{
@ -3183,17 +3200,17 @@ void TypeChecker2::diagnoseMissingTableKey(UnknownProperty* utk, TypeErrorData&
if (auto ttv = getTableType(utk->table))
accumulate(ttv->props);
else if (auto ctv = get<ClassType>(follow(utk->table)))
else if (auto etv = get<ExternType>(follow(utk->table)))
{
while (ctv)
while (etv)
{
accumulate(ctv->props);
accumulate(etv->props);
if (!ctv->parent)
if (!etv->parent)
break;
ctv = get<ClassType>(*ctv->parent);
LUAU_ASSERT(ctv);
etv = get<ExternType>(*etv->parent);
LUAU_ASSERT(etv);
}
}

View file

@ -58,8 +58,6 @@ LUAU_FASTFLAGVARIABLE(LuauIndexTypeFunctionFunctionMetamethods)
LUAU_FASTFLAGVARIABLE(LuauIntersectNotNil)
LUAU_FASTFLAGVARIABLE(LuauSkipNoRefineDuringRefinement)
LUAU_FASTFLAGVARIABLE(LuauMetatablesHaveLength)
LUAU_FASTFLAGVARIABLE(LuauDontForgetToReduceUnionFunc)
LUAU_FASTFLAGVARIABLE(LuauSearchForRefineableType)
LUAU_FASTFLAGVARIABLE(LuauIndexAnyIsAny)
LUAU_FASTFLAGVARIABLE(LuauFixCyclicIndexInIndexer)
LUAU_FASTFLAGVARIABLE(LuauSimplyRefineNotNil)
@ -68,6 +66,7 @@ LUAU_FASTFLAGVARIABLE(LuauNewTypeFunReductionChecks2)
LUAU_FASTFLAGVARIABLE(LuauReduceUnionFollowUnionType)
LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect)
LUAU_FASTFLAGVARIABLE(LuauNarrowIntersectionNevers)
LUAU_FASTFLAGVARIABLE(LuauRefineWaitForBlockedTypesInTarget)
namespace Luau
{
@ -106,7 +105,7 @@ struct InstanceCollector_DEPRECATED : TypeOnceVisitor
cyclicInstance.push_back(t);
}
bool visit(TypeId ty, const ClassType&) override
bool visit(TypeId ty, const ExternType&) override
{
return false;
}
@ -182,7 +181,7 @@ struct InstanceCollector : TypeOnceVisitor
}
}
bool visit(TypeId ty, const ClassType&) override
bool visit(TypeId ty, const ExternType&) override
{
return false;
}
@ -270,7 +269,7 @@ struct UnscopedGenericFinder : TypeOnceVisitor
return false;
}
bool visit(TypeId ty, const ClassType&) override
bool visit(TypeId ty, const ExternType&) override
{
return false;
}
@ -844,7 +843,7 @@ static std::optional<TypeFunctionReductionResult<TypeId>> tryDistributeTypeFunct
{
arguments[unionIndex] = option;
TypeFunctionReductionResult<TypeId> result = f(instance, arguments, packParams, ctx, args...);
TypeFunctionReductionResult<TypeId> result = f(instance, arguments, packParams, ctx, args...); // NOLINT
blockedTypes.insert(blockedTypes.end(), result.blockedTypes.begin(), result.blockedTypes.end());
if (result.reductionStatus != Reduction::MaybeOk)
reductionStatus = result.reductionStatus;
@ -869,7 +868,7 @@ static std::optional<TypeFunctionReductionResult<TypeId>> tryDistributeTypeFunct
{},
});
if (FFlag::LuauDontForgetToReduceUnionFunc && ctx->solver)
if (ctx->solver)
ctx->pushConstraint(ReduceConstraint{resultTy});
return {{resultTy, Reduction::MaybeOk, {}, {}}};
@ -908,7 +907,7 @@ struct FindUserTypeFunctionBlockers : TypeOnceVisitor
return true;
}
bool visit(TypeId ty, const ClassType&) override
bool visit(TypeId ty, const ExternType&) override
{
return false;
}
@ -1840,10 +1839,20 @@ TypeFunctionReductionResult<TypeId> orTypeFunction(
return {rhsTy, Reduction::MaybeOk, {}, {}};
// check to see if both operand types are resolved enough, and wait to reduce if not
if (isPending(lhsTy, ctx->solver))
return {std::nullopt, Reduction::MaybeOk, {lhsTy}, {}};
else if (isPending(rhsTy, ctx->solver))
return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}};
if (FFlag::DebugLuauGreedyGeneralization)
{
if (is<BlockedType, PendingExpansionType, TypeFunctionInstanceType>(lhsTy))
return {std::nullopt, Reduction::MaybeOk, {lhsTy}, {}};
else if (is<BlockedType, PendingExpansionType, TypeFunctionInstanceType>(rhsTy))
return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}};
}
else
{
if (isPending(lhsTy, ctx->solver))
return {std::nullopt, Reduction::MaybeOk, {lhsTy}, {}};
else if (isPending(rhsTy, ctx->solver))
return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}};
}
// Or evalutes to the LHS type if the LHS is truthy, and the RHS type if LHS is falsy.
SimplifyResult filteredLhs = simplifyIntersection(ctx->builtins, ctx->arena, lhsTy, ctx->builtins->truthyType);
@ -1877,10 +1886,20 @@ static TypeFunctionReductionResult<TypeId> comparisonTypeFunction(
if (lhsTy == instance || rhsTy == instance)
return {ctx->builtins->neverType, Reduction::MaybeOk, {}, {}};
if (isPending(lhsTy, ctx->solver))
return {std::nullopt, Reduction::MaybeOk, {lhsTy}, {}};
else if (isPending(rhsTy, ctx->solver))
return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}};
if (FFlag::DebugLuauGreedyGeneralization)
{
if (is<BlockedType, PendingExpansionType, TypeFunctionInstanceType>(lhsTy))
return {std::nullopt, Reduction::MaybeOk, {lhsTy}, {}};
else if (is<BlockedType, PendingExpansionType, TypeFunctionInstanceType>(rhsTy))
return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}};
}
else
{
if (isPending(lhsTy, ctx->solver))
return {std::nullopt, Reduction::MaybeOk, {lhsTy}, {}};
else if (isPending(rhsTy, ctx->solver))
return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}};
}
// Algebra Reduction Rules for comparison type functions
// Note that comparing to never tells you nothing about the other operand
@ -2130,7 +2149,7 @@ struct FindRefinementBlockers : TypeOnceVisitor
return false;
}
bool visit(TypeId ty, const ClassType&) override
bool visit(TypeId ty, const ExternType&) override
{
return false;
}
@ -2241,8 +2260,12 @@ TypeFunctionReductionResult<TypeId> refineTypeFunction(
for (size_t i = 1; i < typeParams.size(); i++)
discriminantTypes.push_back(follow(typeParams.at(i)));
const bool targetIsPending = FFlag::DebugLuauGreedyGeneralization
? is<BlockedType, PendingExpansionType, TypeFunctionInstanceType>(targetTy)
: isPending(targetTy, ctx->solver);
// check to see if both operand types are resolved enough, and wait to reduce if not
if (isPending(targetTy, ctx->solver))
if (targetIsPending)
return {std::nullopt, Reduction::MaybeOk, {targetTy}, {}};
else
{
@ -2252,6 +2275,18 @@ TypeFunctionReductionResult<TypeId> refineTypeFunction(
return {std::nullopt, Reduction::MaybeOk, {t}, {}};
}
}
if (FFlag::LuauRefineWaitForBlockedTypesInTarget)
{
// If we have a blocked type in the target, we *could* potentially
// refine it, but more likely we end up with some type explosion in
// normalization.
FindRefinementBlockers frb;
frb.traverse(targetTy);
if (!frb.found.empty())
return {std::nullopt, Reduction::MaybeOk, {frb.found.begin(), frb.found.end()}, {}};
}
// Refine a target type and a discriminant one at a time.
// Returns result : TypeId, toBlockOn : vector<TypeId>
auto stepRefine = [&ctx](TypeId target, TypeId discriminant) -> std::pair<TypeId, std::vector<TypeId>>
@ -2282,28 +2317,14 @@ TypeFunctionReductionResult<TypeId> refineTypeFunction(
}
else
{
if (FFlag::LuauSearchForRefineableType)
{
// If the discriminant type is only:
// - The `*no-refine*` type or,
// - tables, metatables, unions, intersections, functions, or negations _containing_ `*no-refine*`.
// There's no point in refining against it.
ContainsRefinableType crt;
crt.traverse(discriminant);
if (!crt.found)
return {target, {}};
}
else
{
if (FFlag::LuauSkipNoRefineDuringRefinement)
if (get<NoRefineType>(discriminant))
return {target, {}};
if (auto nt = get<NegationType>(discriminant))
{
if (get<NoRefineType>(follow(nt->ty)))
return {target, {}};
}
}
// If the discriminant type is only:
// - The `*no-refine*` type or,
// - tables, metatables, unions, intersections, functions, or negations _containing_ `*no-refine*`.
// There's no point in refining against it.
ContainsRefinableType crt;
crt.traverse(discriminant);
if (!crt.found)
return {target, {}};
if (FFlag::LuauSimplyRefineNotNil)
{
@ -2329,8 +2350,32 @@ TypeFunctionReductionResult<TypeId> refineTypeFunction(
if (is<TableType>(target) || isSimpleDiscriminant(discriminant))
{
SimplifyResult result = simplifyIntersection(ctx->builtins, ctx->arena, target, discriminant);
if (!result.blockedTypes.empty())
return {nullptr, {result.blockedTypes.begin(), result.blockedTypes.end()}};
if (FFlag::DebugLuauGreedyGeneralization)
{
// Simplification considers free and generic types to be
// 'blocking', but that's not suitable for refine<>.
//
// If we are only blocked on those types, we consider
// the simplification a success and reduce.
if (std::all_of(
begin(result.blockedTypes),
end(result.blockedTypes),
[](auto&& v)
{
return is<FreeType, GenericType>(follow(v));
}
))
{
return {result.result, {}};
}
else
return {nullptr, {result.blockedTypes.begin(), result.blockedTypes.end()}};
}
else
{
if (!result.blockedTypes.empty())
return {nullptr, {result.blockedTypes.begin(), result.blockedTypes.end()}};
}
return {result.result, {}};
}
}
@ -2684,7 +2729,7 @@ bool computeKeysOf(TypeId ty, Set<std::string>& result, DenseHashSet<TypeId>& se
return res;
}
if (auto classTy = get<ClassType>(ty))
if (auto classTy = get<ExternType>(ty))
{
for (auto [key, _] : classTy->props)
result.insert(key);
@ -2707,7 +2752,7 @@ bool computeKeysOf(TypeId ty, Set<std::string>& result, DenseHashSet<TypeId>& se
return res;
}
// this should not be reachable since the type should be a valid tables or classes part from normalization.
// this should not be reachable since the type should be a valid tables or extern types part from normalization.
LUAU_ASSERT(false);
return false;
}
@ -2733,9 +2778,9 @@ TypeFunctionReductionResult<TypeId> keyofFunctionImpl(
if (!normTy)
return {std::nullopt, Reduction::MaybeOk, {}, {}};
// if we don't have either just tables or just classes, we've got nothing to get keys of (at least until a future version perhaps adds classes
// if we don't have either just tables or just extern types, we've got nothing to get keys of (at least until a future version perhaps adds extern types
// as well)
if (normTy->hasTables() == normTy->hasClasses())
if (normTy->hasTables() == normTy->hasExternTypes())
return {std::nullopt, Reduction::Erroneous, {}, {}};
// this is sort of atrocious, but we're trying to reject any type that has not normalized to a table or a union of tables.
@ -2746,31 +2791,31 @@ TypeFunctionReductionResult<TypeId> keyofFunctionImpl(
// we're going to collect the keys in here
Set<std::string> keys{{}};
// computing the keys for classes
if (normTy->hasClasses())
// computing the keys for extern types
if (normTy->hasExternTypes())
{
LUAU_ASSERT(!normTy->hasTables());
// seen set for key computation for classes
// seen set for key computation for extern types
DenseHashSet<TypeId> seen{{}};
auto classesIter = normTy->classes.ordering.begin();
auto classesIterEnd = normTy->classes.ordering.end();
LUAU_ASSERT(classesIter != classesIterEnd); // should be guaranteed by the `hasClasses` check earlier
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(*classesIter, keys, seen, isRaw, ctx))
if (!computeKeysOf(*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 (++classesIter != classesIterEnd)
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(*classesIter, localKeys, seen, isRaw, ctx))
if (!computeKeysOf(*externTypeIter, localKeys, seen, isRaw, ctx))
continue;
for (auto& key : keys)
@ -2785,7 +2830,7 @@ TypeFunctionReductionResult<TypeId> keyofFunctionImpl(
// computing the keys for tables
if (normTy->hasTables())
{
LUAU_ASSERT(!normTy->hasClasses());
LUAU_ASSERT(!normTy->hasExternTypes());
// seen set for key computation for tables
DenseHashSet<TypeId> seen{{}};
@ -2947,7 +2992,7 @@ bool searchPropsAndIndexer(
return false;
}
/* Handles recursion / metamethods of tables/classes
/* Handles recursion / metamethods of tables and extern types
`isRaw` parameter indicates whether or not we should follow __index metamethods
returns false if property of `ty` could not be found */
bool tblIndexInto_DEPRECATED(TypeId indexer, TypeId indexee, DenseHashSet<TypeId>& result, NotNull<TypeFunctionContext> ctx, bool isRaw)
@ -3122,11 +3167,11 @@ TypeFunctionReductionResult<TypeId> indexFunctionImpl(
return {ctx->builtins->anyType, Reduction::MaybeOk, {}, {}};
}
// if we don't have either just tables or just classes, we've got nothing to index into
if (indexeeNormTy->hasTables() == indexeeNormTy->hasClasses())
// if we don't have either just tables or just extern types, we've got nothing to index into
if (indexeeNormTy->hasTables() == indexeeNormTy->hasExternTypes())
return {std::nullopt, Reduction::Erroneous, {}, {}};
// we're trying to reject any type that has not normalized to a table/class or a union of tables/classes.
// we're trying to reject any type that has not normalized to a table or extern type or a union of tables or extern types.
if (indexeeNormTy->hasTops() || indexeeNormTy->hasBooleans() || indexeeNormTy->hasErrors() || indexeeNormTy->hasNils() ||
indexeeNormTy->hasNumbers() || indexeeNormTy->hasStrings() || indexeeNormTy->hasThreads() || indexeeNormTy->hasBuffers() ||
indexeeNormTy->hasFunctions() || indexeeNormTy->hasTyvars())
@ -3157,18 +3202,18 @@ TypeFunctionReductionResult<TypeId> indexFunctionImpl(
DenseHashSet<TypeId> properties{{}}; // vector of types that will be returned
if (indexeeNormTy->hasClasses())
if (indexeeNormTy->hasExternTypes())
{
LUAU_ASSERT(!indexeeNormTy->hasTables());
if (isRaw) // rawget should never reduce for classes (to match the behavior of the rawget global function)
if (isRaw) // rawget should never reduce for extern types (to match the behavior of the rawget global function)
return {std::nullopt, Reduction::Erroneous, {}, {}};
// at least one class is guaranteed to be in the iterator by .hasClasses()
for (auto classesIter = indexeeNormTy->classes.ordering.begin(); classesIter != indexeeNormTy->classes.ordering.end(); ++classesIter)
// at least one class is guaranteed to be in the iterator by .hasExternTypes()
for (auto externTypeIter = indexeeNormTy->externTypes.ordering.begin(); externTypeIter != indexeeNormTy->externTypes.ordering.end(); ++externTypeIter)
{
auto classTy = get<ClassType>(*classesIter);
if (!classTy)
auto externTy = get<ExternType>(*externTypeIter);
if (!externTy)
{
LUAU_ASSERT(false); // this should not be possible according to normalization's spec
return {std::nullopt, Reduction::Erroneous, {}, {}};
@ -3177,16 +3222,16 @@ TypeFunctionReductionResult<TypeId> indexFunctionImpl(
for (TypeId ty : *typesToFind)
{
// Search for all instances of indexer in class->props and class->indexer
if (searchPropsAndIndexer(ty, classTy->props, classTy->indexer, properties, ctx))
if (searchPropsAndIndexer(ty, externTy->props, externTy->indexer, properties, ctx))
continue; // Indexer was found in this class, so we can move on to the next
auto parent = classTy->parent;
auto parent = externTy->parent;
bool foundInParent = false;
while (parent && !foundInParent)
{
auto parentClass = get<ClassType>(follow(*parent));
foundInParent = searchPropsAndIndexer(ty, parentClass->props, parentClass->indexer, properties, ctx);
parent = parentClass->parent;
auto parentExternType = get<ExternType>(follow(*parent));
foundInParent = searchPropsAndIndexer(ty, parentExternType->props, parentExternType->indexer, properties, ctx);
parent = parentExternType->parent;
}
// we move on to the next type if any of the parents we went through had the property.
@ -3198,7 +3243,7 @@ TypeFunctionReductionResult<TypeId> indexFunctionImpl(
// 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, *classesIter, "__index", Location{});
std::optional<TypeId> mmType = findMetatableEntry(ctx->builtins, dummy, *externTypeIter, "__index", Location{});
if (!mmType) // if a metatable does not exist, there is no where else to look
return {std::nullopt, Reduction::Erroneous, {}, {}};
@ -3210,7 +3255,7 @@ TypeFunctionReductionResult<TypeId> indexFunctionImpl(
if (indexeeNormTy->hasTables())
{
LUAU_ASSERT(!indexeeNormTy->hasClasses());
LUAU_ASSERT(!indexeeNormTy->hasExternTypes());
// at least one table is guaranteed to be in the iterator by .hasTables()
for (auto tablesIter = indexeeNormTy->tables.begin(); tablesIter != indexeeNormTy->tables.end(); ++tablesIter)
@ -3305,7 +3350,7 @@ TypeFunctionReductionResult<TypeId> setmetatableTypeFunction(
// we're trying to reject any type that has not normalized to a table or a union/intersection of tables.
if (targetNorm->hasTops() || targetNorm->hasBooleans() || targetNorm->hasErrors() || targetNorm->hasNils() || targetNorm->hasNumbers() ||
targetNorm->hasStrings() || targetNorm->hasThreads() || targetNorm->hasBuffers() || targetNorm->hasFunctions() || targetNorm->hasTyvars() ||
targetNorm->hasClasses())
targetNorm->hasExternTypes())
return {std::nullopt, Reduction::Erroneous, {}, {}};
// if the supposed metatable is not a table, we will fail to reduce.
@ -3379,7 +3424,7 @@ static TypeFunctionReductionResult<TypeId> getmetatableHelper(TypeId targetTy, c
erroneous = false;
}
if (auto clazz = get<ClassType>(targetTy))
if (auto clazz = get<ExternType>(targetTy))
{
metatable = clazz->metatable;
erroneous = false;
@ -3530,7 +3575,7 @@ BuiltinTypeFunctions::BuiltinTypeFunctions()
, ltFunc{"lt", ltTypeFunction}
, leFunc{"le", leTypeFunction}
, eqFunc{"eq", eqTypeFunction}
, refineFunc{"refine", refineTypeFunction}
, refineFunc{"refine", refineTypeFunction, /*canReduceGenerics*/ FFlag::DebugLuauGreedyGeneralization}
, singletonFunc{"singleton", singletonTypeFunction}
, unionFunc{"union", unionTypeFunction}
, intersectFunc{"intersect", intersectTypeFunction}

View file

@ -46,7 +46,7 @@ struct InstanceCollector2 : TypeOnceVisitor
cyclicInstance.insert(t);
}
bool visit(TypeId ty, const ClassType&) override
bool visit(TypeId ty, const ExternType&) override
{
return false;
}

View file

@ -15,6 +15,7 @@
LUAU_DYNAMIC_FASTINT(LuauTypeFunctionSerdeIterationLimit)
LUAU_FASTFLAGVARIABLE(LuauTypeFunReadWriteParents)
LUAU_FASTFLAGVARIABLE(LuauTypeFunOptional)
namespace Luau
{
@ -154,7 +155,7 @@ static std::string getTag(lua_State* L, TypeFunctionTypeId ty)
return "table";
else if (get<TypeFunctionFunctionType>(ty))
return "function";
else if (get<TypeFunctionClassType>(ty))
else if (get<TypeFunctionExternType>(ty))
return "class";
else if (get<TypeFunctionGenericType>(ty))
return "generic";
@ -315,6 +316,38 @@ static int getSingletonValue(lua_State* L)
luaL_error(L, "type.value: can't call `value` method on `%s` type", getTag(L, self).c_str());
}
// Luau: `types.optional(ty: type) -> type`
// Returns the type instance representing an optional version of `ty`.
// If `ty` is a union, this adds `nil` to the components of the union.
// Otherwise, makes a union of the two things.
static int createOptional(lua_State* L)
{
LUAU_ASSERT(FFlag::LuauTypeFunOptional);
int argumentCount = lua_gettop(L);
if (argumentCount != 1)
luaL_error(L, "types.optional: expected 1 argument, but got %d", argumentCount);
TypeFunctionTypeId argument = getTypeUserData(L, 1);
std::vector<TypeFunctionTypeId> components;
if (auto unionTy = get<TypeFunctionUnionType>(argument))
{
components.reserve(unionTy->components.size() + 1);
components.insert(components.begin(), unionTy->components.begin(), unionTy->components.end());
}
else
components.emplace_back(argument);
components.emplace_back(allocateTypeFunctionType(L, TypeFunctionPrimitiveType(TypeFunctionPrimitiveType::NilType)));
allocTypeUserData(L, TypeFunctionUnionType{components});
return 1;
}
// Luau: `types.unionof(...: type) -> type`
// Returns the type instance representing union
static int createUnion(lua_State* L)
@ -1114,7 +1147,7 @@ static int getClassParent_DEPRECATED(lua_State* L)
luaL_error(L, "type.parent: expected 1 arguments, but got %d", argumentCount);
TypeFunctionTypeId self = getTypeUserData(L, 1);
auto tfct = get<TypeFunctionClassType>(self);
auto tfct = get<TypeFunctionExternType>(self);
if (!tfct)
luaL_error(L, "type.parent: expected self to be a class, but got %s instead", getTag(L, self).c_str());
@ -1136,7 +1169,7 @@ static int getReadParent(lua_State* L)
luaL_error(L, "type.parent: expected 1 arguments, but got %d", argumentCount);
TypeFunctionTypeId self = getTypeUserData(L, 1);
auto tfct = get<TypeFunctionClassType>(self);
auto tfct = get<TypeFunctionExternType>(self);
if (!tfct)
luaL_error(L, "type.parent: expected self to be a class, but got %s instead", getTag(L, self).c_str());
@ -1158,7 +1191,7 @@ static int getWriteParent(lua_State* L)
luaL_error(L, "type.parent: expected 1 arguments, but got %d", argumentCount);
TypeFunctionTypeId self = getTypeUserData(L, 1);
auto tfct = get<TypeFunctionClassType>(self);
auto tfct = get<TypeFunctionExternType>(self);
if (!tfct)
luaL_error(L, "type.parent: expected self to be a class, but got %s instead", getTag(L, self).c_str());
@ -1242,7 +1275,7 @@ static int getProps(lua_State* L)
return 1;
}
if (auto tfct = get<TypeFunctionClassType>(self))
if (auto tfct = get<TypeFunctionExternType>(self))
{
lua_createtable(L, int(tfct->props.size()), 0);
for (auto& [name, prop] : tfct->props)
@ -1305,7 +1338,7 @@ static int getIndexer(lua_State* L)
return 1;
}
if (auto tfct = get<TypeFunctionClassType>(self))
if (auto tfct = get<TypeFunctionExternType>(self))
{
// if the indexer does not exist, we should return nil
if (!tfct->indexer.has_value())
@ -1353,7 +1386,7 @@ static int getReadIndexer(lua_State* L)
return 1;
}
if (auto tfct = get<TypeFunctionClassType>(self))
if (auto tfct = get<TypeFunctionExternType>(self))
{
// if the indexer does not exist, we should return nil
if (!tfct->indexer.has_value())
@ -1399,7 +1432,7 @@ static int getWriteIndexer(lua_State* L)
return 1;
}
if (auto tfct = get<TypeFunctionClassType>(self))
if (auto tfct = get<TypeFunctionExternType>(self))
{
// if the indexer does not exist, we should return nil
if (!tfct->indexer.has_value())
@ -1439,7 +1472,7 @@ static int getMetatable(lua_State* L)
return 1;
}
if (auto tfct = get<TypeFunctionClassType>(self))
if (auto tfct = get<TypeFunctionExternType>(self))
{
// if the metatable does not exist, we should return nil
if (!tfct->metatable.has_value())
@ -1524,6 +1557,7 @@ void registerTypesLibrary(lua_State* L)
{"copy", deepCopy},
{"generic", createGeneric},
{(FFlag::LuauTypeFunOptional) ? "optional" : nullptr, (FFlag::LuauTypeFunOptional) ? createOptional : nullptr},
{nullptr, nullptr}
};
@ -1593,7 +1627,7 @@ void registerTypeUserData(lua_State* L)
// Union and Intersection type methods
{"components", getComponents},
// Class type methods
// Extern type methods
{FFlag::LuauTypeFunReadWriteParents ? "readparent" : "parent", FFlag::LuauTypeFunReadWriteParents ? getReadParent : getClassParent_DEPRECATED},
// Function type methods (cont.)
@ -1604,7 +1638,7 @@ void registerTypeUserData(lua_State* L)
{"name", getGenericName},
{"ispack", getGenericIsPack},
// move this under Class type methods when removing FFlagLuauTypeFunReadWriteParents
// move this under extern type methods when removing FFlagLuauTypeFunReadWriteParents
{FFlag::LuauTypeFunReadWriteParents ? "writeparent" : nullptr, FFlag::LuauTypeFunReadWriteParents ? getWriteParent : nullptr},
{nullptr, nullptr}
@ -1903,12 +1937,12 @@ bool areEqual(SeenSet& seen, const TypeFunctionFunctionType& lhs, const TypeFunc
return true;
}
bool areEqual(SeenSet& seen, const TypeFunctionClassType& lhs, const TypeFunctionClassType& rhs)
bool areEqual(SeenSet& seen, const TypeFunctionExternType& lhs, const TypeFunctionExternType& rhs)
{
if (seenSetContains(seen, &lhs, &rhs))
return true;
return lhs.classTy == rhs.classTy;
return lhs.externTy == rhs.externTy;
}
bool areEqual(SeenSet& seen, const TypeFunctionType& lhs, const TypeFunctionType& rhs)
@ -1976,8 +2010,8 @@ bool areEqual(SeenSet& seen, const TypeFunctionType& lhs, const TypeFunctionType
}
{
const TypeFunctionClassType* lf = get<TypeFunctionClassType>(&lhs);
const TypeFunctionClassType* rf = get<TypeFunctionClassType>(&rhs);
const TypeFunctionExternType* lf = get<TypeFunctionExternType>(&lhs);
const TypeFunctionExternType* rf = get<TypeFunctionExternType>(&rhs);
if (lf && rf)
return areEqual(seen, *lf, *rf);
}
@ -2266,7 +2300,7 @@ private:
TypeFunctionTypePackId emptyTypePack = typeFunctionRuntime->typePackArena.allocate(TypeFunctionTypePack{});
target = typeFunctionRuntime->typeArena.allocate(TypeFunctionFunctionType{{}, {}, emptyTypePack, emptyTypePack});
}
else if (auto c = get<TypeFunctionClassType>(ty))
else if (auto c = get<TypeFunctionExternType>(ty))
target = ty; // Don't copy a class since they are immutable
else if (auto g = get<TypeFunctionGenericType>(ty))
target = typeFunctionRuntime->typeArena.allocate(TypeFunctionGenericType{g->isNamed, g->isPack, g->name});
@ -2321,7 +2355,7 @@ private:
cloneChildren(t1, t2);
else if (auto [f1, f2] = std::tuple{getMutable<TypeFunctionFunctionType>(ty), getMutable<TypeFunctionFunctionType>(tfti)}; f1 && f2)
cloneChildren(f1, f2);
else if (auto [c1, c2] = std::tuple{getMutable<TypeFunctionClassType>(ty), getMutable<TypeFunctionClassType>(tfti)}; c1 && c2)
else if (auto [c1, c2] = std::tuple{getMutable<TypeFunctionExternType>(ty), getMutable<TypeFunctionExternType>(tfti)}; c1 && c2)
cloneChildren(c1, c2);
else if (auto [g1, g2] = std::tuple{getMutable<TypeFunctionGenericType>(ty), getMutable<TypeFunctionGenericType>(tfti)}; g1 && g2)
cloneChildren(g1, g2);
@ -2431,7 +2465,7 @@ private:
f2->retTypes = shallowClone(f1->retTypes);
}
void cloneChildren(TypeFunctionClassType* c1, TypeFunctionClassType* c2)
void cloneChildren(TypeFunctionExternType* c1, TypeFunctionExternType* c2)
{
// noop.
}

View file

@ -206,12 +206,12 @@ private:
TypeFunctionTypePackId emptyTypePack = typeFunctionRuntime->typePackArena.allocate(TypeFunctionTypePack{});
target = typeFunctionRuntime->typeArena.allocate(TypeFunctionFunctionType{{}, {}, emptyTypePack, emptyTypePack});
}
else if (auto c = get<ClassType>(ty))
else if (auto c = get<ExternType>(ty))
{
// Since there aren't any new class types being created in type functions, we will deserialize by using a direct reference to the original
// class
target = typeFunctionRuntime->typeArena.allocate(
TypeFunctionClassType{{}, std::nullopt, std::nullopt, std::nullopt, std::nullopt, std::nullopt, ty}
TypeFunctionExternType{{}, std::nullopt, std::nullopt, std::nullopt, std::nullopt, std::nullopt, ty}
);
}
else if (auto g = get<GenericType>(ty))
@ -291,7 +291,7 @@ private:
serializeChildren(m1, m2);
else if (auto [f1, f2] = std::tuple{get<FunctionType>(ty), getMutable<TypeFunctionFunctionType>(tfti)}; f1 && f2)
serializeChildren(f1, f2);
else if (auto [c1, c2] = std::tuple{get<ClassType>(ty), getMutable<TypeFunctionClassType>(tfti)}; c1 && c2)
else if (auto [c1, c2] = std::tuple{get<ExternType>(ty), getMutable<TypeFunctionExternType>(tfti)}; c1 && c2)
serializeChildren(c1, c2);
else if (auto [g1, g2] = std::tuple{get<GenericType>(ty), getMutable<TypeFunctionGenericType>(tfti)}; g1 && g2)
serializeChildren(g1, g2);
@ -411,7 +411,7 @@ private:
f2->retTypes = shallowSerialize(f1->retTypes);
}
void serializeChildren(const ClassType* c1, TypeFunctionClassType* c2)
void serializeChildren(const ExternType* c1, TypeFunctionExternType* c2)
{
for (const auto& [k, p] : c1->props)
{
@ -702,9 +702,9 @@ private:
TypePackId emptyTypePack = state->ctx->arena->addTypePack(TypePack{});
target = state->ctx->arena->addType(FunctionType{emptyTypePack, emptyTypePack, {}, false});
}
else if (auto c = get<TypeFunctionClassType>(ty))
else if (auto c = get<TypeFunctionExternType>(ty))
{
target = c->classTy;
target = c->externTy;
}
else if (auto g = get<TypeFunctionGenericType>(ty))
{
@ -811,7 +811,7 @@ private:
deserializeChildren(m2, m1);
else if (auto [f1, f2] = std::tuple{getMutable<FunctionType>(ty), getMutable<TypeFunctionFunctionType>(tfti)}; f1 && f2)
deserializeChildren(f2, f1);
else if (auto [c1, c2] = std::tuple{getMutable<ClassType>(ty), getMutable<TypeFunctionClassType>(tfti)}; c1 && c2)
else if (auto [c1, c2] = std::tuple{getMutable<ExternType>(ty), getMutable<TypeFunctionExternType>(tfti)}; c1 && c2)
deserializeChildren(c2, c1);
else if (auto [g1, g2] = std::tuple{getMutable<GenericType>(ty), getMutable<TypeFunctionGenericType>(tfti)}; g1 && g2)
deserializeChildren(g2, g1);
@ -972,7 +972,7 @@ private:
f1->retTypes = shallowDeserialize(f2->retTypes);
}
void deserializeChildren(TypeFunctionClassType* c2, ClassType* c1)
void deserializeChildren(TypeFunctionExternType* c2, ExternType* c1)
{
// noop.
}

View file

@ -32,8 +32,8 @@ LUAU_FASTINTVARIABLE(LuauVisitRecursionLimit, 500)
LUAU_FASTFLAG(LuauKnowsTheDataModel3)
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification)
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAG(LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
LUAU_FASTFLAG(LuauRetainDefinitionAliasLocations)
LUAU_FASTFLAGVARIABLE(LuauStatForInFix)
LUAU_FASTFLAGVARIABLE(LuauReduceCheckBinaryExprStackPressure)
@ -44,7 +44,7 @@ namespace Luau
static bool typeCouldHaveMetatable(TypeId ty)
{
return get<TableType>(follow(ty)) || get<ClassType>(follow(ty)) || get<MetatableType>(follow(ty));
return get<TableType>(follow(ty)) || get<ExternType>(follow(ty)) || get<MetatableType>(follow(ty));
}
static void defaultLuauPrintLine(const std::string& s)
@ -318,7 +318,7 @@ ModulePtr TypeChecker::checkWithoutRecursionCheck(const SourceModule& module, Mo
unifierState.skipCacheForType.clear();
duplicateTypeAliases.clear();
incorrectClassDefinitions.clear();
incorrectExternTypeDefinitions.clear();
return std::move(currentModule);
}
@ -383,7 +383,7 @@ ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStat& program)
}
else if (auto global = program.as<AstStatDeclareFunction>())
return check(scope, *global);
else if (auto global = program.as<AstStatDeclareClass>())
else if (auto global = program.as<AstStatDeclareExternType>())
return check(scope, *global);
else if (auto errorStatement = program.as<AstStatError>())
{
@ -498,9 +498,9 @@ ControlFlow TypeChecker::checkBlockWithoutRecursionCheck(const ScopePtr& scope,
prototype(scope, *typealias, subLevel);
++subLevel;
}
else if (const auto& declaredClass = stat->as<AstStatDeclareClass>())
else if (const auto& declaredExternType = stat->as<AstStatDeclareExternType>())
{
prototype(scope, *declaredClass);
prototype(scope, *declaredExternType);
}
}
@ -788,7 +788,7 @@ struct Demoter : Substitution
bool ignoreChildren(TypeId ty) override
{
if (get<ClassType>(ty))
if (get<ExternType>(ty))
return true;
return false;
@ -798,8 +798,7 @@ struct Demoter : Substitution
{
auto ftv = get<FreeType>(ty);
LUAU_ASSERT(ftv);
return FFlag::LuauFreeTypesMustHaveBounds ? arena->freshType(builtins, demotedLevel(ftv->level))
: addType(FreeType{demotedLevel(ftv->level)});
return arena->freshType(builtins, demotedLevel(ftv->level));
}
TypePackId clean(TypePackId tp) override
@ -1686,82 +1685,82 @@ void TypeChecker::prototype(const ScopePtr& scope, const AstStatTypeAlias& typea
}
}
void TypeChecker::prototype(const ScopePtr& scope, const AstStatDeclareClass& declaredClass)
void TypeChecker::prototype(const ScopePtr& scope, const AstStatDeclareExternType& declaredExternType)
{
std::optional<TypeId> superTy = std::make_optional(builtinTypes->classType);
if (declaredClass.superName)
std::optional<TypeId> superTy = std::make_optional(builtinTypes->externType);
if (declaredExternType.superName)
{
Name superName = Name(declaredClass.superName->value);
Name superName = Name(declaredExternType.superName->value);
std::optional<TypeFun> lookupType = scope->lookupType(superName);
if (!lookupType)
{
reportError(declaredClass.location, UnknownSymbol{superName, UnknownSymbol::Type});
incorrectClassDefinitions.insert(&declaredClass);
reportError(declaredExternType.location, UnknownSymbol{superName, UnknownSymbol::Type});
incorrectExternTypeDefinitions.insert(&declaredExternType);
return;
}
// We don't have generic classes, so this assertion _should_ never be hit.
// We don't have generic extern types, so this assertion _should_ never be hit.
LUAU_ASSERT(lookupType->typeParams.size() == 0 && lookupType->typePackParams.size() == 0);
superTy = lookupType->type;
if (!get<ClassType>(follow(*superTy)))
if (!get<ExternType>(follow(*superTy)))
{
reportError(
declaredClass.location,
GenericError{format("Cannot use non-class type '%s' as a superclass of class '%s'", superName.c_str(), declaredClass.name.value)}
declaredExternType.location,
GenericError{format("Cannot use non-class type '%s' as a superclass of class '%s'", superName.c_str(), declaredExternType.name.value)}
);
incorrectClassDefinitions.insert(&declaredClass);
incorrectExternTypeDefinitions.insert(&declaredExternType);
return;
}
}
Name className(declaredClass.name.value);
Name className(declaredExternType.name.value);
TypeId classTy = addType(ClassType(className, {}, superTy, std::nullopt, {}, {}, currentModule->name, declaredClass.location));
ClassType* ctv = getMutable<ClassType>(classTy);
TypeId classTy = addType(ExternType(className, {}, superTy, std::nullopt, {}, {}, currentModule->name, declaredExternType.location));
ExternType* etv = getMutable<ExternType>(classTy);
TypeId metaTy = addType(TableType{TableState::Sealed, scope->level});
ctv->metatable = metaTy;
etv->metatable = metaTy;
if (FFlag::LuauRetainDefinitionAliasLocations)
scope->exportedTypeBindings[className] = TypeFun{{}, classTy, declaredClass.location};
scope->exportedTypeBindings[className] = TypeFun{{}, classTy, declaredExternType.location};
else
scope->exportedTypeBindings[className] = TypeFun{{}, classTy};
}
ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatDeclareClass& declaredClass)
ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatDeclareExternType& declaredExternType)
{
Name className(declaredClass.name.value);
Name className(declaredExternType.name.value);
// Don't bother checking if the class definition was incorrect
if (incorrectClassDefinitions.find(&declaredClass))
if (incorrectExternTypeDefinitions.find(&declaredExternType))
return ControlFlow::None;
std::optional<TypeFun> binding;
if (auto it = scope->exportedTypeBindings.find(className); it != scope->exportedTypeBindings.end())
binding = it->second;
// This class definition must have been `prototype()`d first.
// This extern type definition must have been `prototype()`d first.
if (!binding)
ice("Class not predeclared");
ice("Extern type not predeclared");
TypeId classTy = binding->type;
ClassType* ctv = getMutable<ClassType>(classTy);
TypeId externTy = binding->type;
ExternType* etv = getMutable<ExternType>(externTy);
if (!ctv->metatable)
ice("No metatable for declared class");
if (!etv->metatable)
ice("No metatable for declared extern type");
if (const auto& indexer = declaredClass.indexer)
ctv->indexer = TableIndexer(resolveType(scope, *indexer->indexType), resolveType(scope, *indexer->resultType));
if (const auto& indexer = declaredExternType.indexer)
etv->indexer = TableIndexer(resolveType(scope, *indexer->indexType), resolveType(scope, *indexer->resultType));
TableType* metatable = getMutable<TableType>(*ctv->metatable);
for (const AstDeclaredClassProp& prop : declaredClass.props)
TableType* metatable = getMutable<TableType>(*etv->metatable);
for (const AstDeclaredExternTypeProperty& prop : declaredExternType.props)
{
Name propName(prop.name.value);
TypeId propTy = resolveType(scope, *prop.ty);
bool assignToMetatable = isMetamethod(propName);
Luau::ClassType::Props& assignTo = assignToMetatable ? metatable->props : ctv->props;
Luau::ExternType::Props& assignTo = assignToMetatable ? metatable->props : etv->props;
// Function types always take 'self', but this isn't reflected in the
// parsed annotation. Add it here.
@ -1770,7 +1769,7 @@ ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatDeclareClass&
if (FunctionType* ftv = getMutable<FunctionType>(propTy))
{
ftv->argNames.insert(ftv->argNames.begin(), FunctionArgument{"self", {}});
ftv->argTypes = addTypePack(TypePack{{classTy}, ftv->argTypes});
ftv->argTypes = addTypePack(TypePack{{externTy}, ftv->argTypes});
ftv->hasSelf = true;
FunctionDefinition defn;
@ -1813,7 +1812,7 @@ ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatDeclareClass&
}
else
{
reportError(declaredClass.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())});
}
}
}
@ -1852,7 +1851,8 @@ ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatDeclareFuncti
);
TypePackId argPack = resolveTypePack(funScope, global.params);
TypePackId retPack = resolveTypePack(funScope, global.retTypes);
TypePackId retPack =
FFlag::LuauStoreReturnTypesAsPackOnAst ? resolveTypePack(funScope, *global.retTypes) : resolveTypePack(funScope, global.retTypes_DEPRECATED);
FunctionDefinition defn;
@ -2137,9 +2137,9 @@ std::optional<TypeId> TypeChecker::getIndexTypeFromTypeImpl(
if (auto found = findTablePropertyRespectingMeta(type, name, location, addErrors))
return *found;
}
else if (const ClassType* cls = get<ClassType>(type))
else if (const ExternType* cls = get<ExternType>(type))
{
const Property* prop = lookupClassProp(cls, name);
const Property* prop = lookupExternTypeProp(cls, name);
if (prop)
return prop->type();
@ -3462,14 +3462,14 @@ TypeId TypeChecker::checkLValueBinding(const ScopePtr& scope, const AstExprIndex
return errorRecoveryType(scope);
}
}
else if (const ClassType* lhsClass = get<ClassType>(lhs))
else if (const ExternType* lhsExternType = get<ExternType>(lhs))
{
if (const Property* prop = lookupClassProp(lhsClass, name))
if (const Property* prop = lookupExternTypeProp(lhsExternType, name))
{
return prop->type();
}
if (auto indexer = lhsClass->indexer)
if (auto indexer = lhsExternType->indexer)
{
Unifier state = mkUnifier(scope, expr.location);
state.tryUnify(stringType, indexer->indexType);
@ -3521,14 +3521,14 @@ TypeId TypeChecker::checkLValueBinding(const ScopePtr& scope, const AstExprIndex
if (value)
{
if (const ClassType* exprClass = get<ClassType>(exprType))
if (const ExternType* exprExternType = get<ExternType>(exprType))
{
if (const Property* prop = lookupClassProp(exprClass, value->value.data))
if (const Property* prop = lookupExternTypeProp(exprExternType, value->value.data))
{
return prop->type();
}
if (auto indexer = exprClass->indexer)
if (auto indexer = exprExternType->indexer)
{
unify(stringType, indexer->indexType, scope, expr.index->location);
return indexer->indexResultType;
@ -3554,20 +3554,20 @@ TypeId TypeChecker::checkLValueBinding(const ScopePtr& scope, const AstExprIndex
}
else
{
if (const ClassType* exprClass = get<ClassType>(exprType))
if (const ExternType* exprExternType = get<ExternType>(exprType))
{
if (auto indexer = exprClass->indexer)
if (auto indexer = exprExternType->indexer)
{
unify(indexType, indexer->indexType, scope, expr.index->location);
return indexer->indexResultType;
}
}
if (const ClassType* exprClass = get<ClassType>(exprType))
if (const ExternType* exprExternType = get<ExternType>(exprType))
{
if (isNonstrictMode())
return unknownType;
reportError(TypeError{expr.location, DynamicPropertyLookupOnClassesUnsafe{exprType}});
reportError(TypeError{expr.location, DynamicPropertyLookupOnExternTypesUnsafe{exprType}});
return errorRecoveryType(scope);
}
}
@ -3848,8 +3848,10 @@ std::pair<TypeId, ScopePtr> TypeChecker::checkFunctionSignature(
auto [generics, genericPacks] = createGenericTypes(funScope, std::nullopt, expr, expr.generics, expr.genericPacks);
TypePackId retPack;
if (expr.returnAnnotation)
if (FFlag::LuauStoreReturnTypesAsPackOnAst && expr.returnAnnotation)
retPack = resolveTypePack(funScope, *expr.returnAnnotation);
else if (!FFlag::LuauStoreReturnTypesAsPackOnAst && expr.returnAnnotation_DEPRECATED)
retPack = resolveTypePack(funScope, *expr.returnAnnotation_DEPRECATED);
else if (isNonstrictMode())
retPack = anyTypePack;
else if (expectedFunctionType && expectedFunctionType->generics.empty() && expectedFunctionType->genericPacks.empty())
@ -4056,7 +4058,8 @@ void TypeChecker::checkFunctionBody(const ScopePtr& scope, TypeId ty, const AstE
// 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
// we report it regardless.
if (!isNonstrictMode() || function.returnAnnotation)
if (!isNonstrictMode() ||
(FFlag::LuauStoreReturnTypesAsPackOnAst ? function.returnAnnotation != nullptr : function.returnAnnotation_DEPRECATED.has_value()))
{
reportError(getEndLocation(function), FunctionExitsWithoutReturning{funTy->retTypes});
}
@ -4611,9 +4614,9 @@ std::unique_ptr<WithPredicate<TypePackId>> TypeChecker::checkCallOverload(
{
callTy = getIndexTypeFromType(scope, mttv->metatable, "__call", expr.func->location, /* addErrors= */ false);
}
else if (const ClassType* ctv = get<ClassType>(fn); ctv && ctv->metatable)
else if (const ExternType* etv = get<ExternType>(fn); etv && etv->metatable)
{
callTy = getIndexTypeFromType(scope, *ctv->metatable, "__call", expr.func->location, /* addErrors= */ false);
callTy = getIndexTypeFromType(scope, *etv->metatable, "__call", expr.func->location, /* addErrors= */ false);
}
if (callTy)
@ -5316,17 +5319,17 @@ void TypeChecker::diagnoseMissingTableKey(UnknownProperty* utk, TypeErrorData& d
if (auto ttv = getTableType(utk->table))
accumulate(ttv->props);
else if (auto ctv = get<ClassType>(follow(utk->table)))
else if (auto etv = get<ExternType>(follow(utk->table)))
{
while (ctv)
while (etv)
{
accumulate(ctv->props);
accumulate(etv->props);
if (!ctv->parent)
if (!etv->parent)
break;
ctv = get<ClassType>(*ctv->parent);
LUAU_ASSERT(ctv);
etv = get<ExternType>(*etv->parent);
LUAU_ASSERT(etv);
}
}
@ -5405,8 +5408,7 @@ TypeId TypeChecker::freshType(const ScopePtr& scope)
TypeId TypeChecker::freshType(TypeLevel level)
{
return FFlag::LuauFreeTypesMustHaveBounds ? currentModule->internalTypes.freshType(builtinTypes, level)
: currentModule->internalTypes.addType(Type(FreeType(level)));
return currentModule->internalTypes.freshType(builtinTypes, level);
}
TypeId TypeChecker::singletonType(bool value)
@ -5803,7 +5805,8 @@ TypeId TypeChecker::resolveTypeWorker(const ScopePtr& scope, const AstType& anno
auto [generics, genericPacks] = createGenericTypes(funcScope, std::nullopt, annotation, func->generics, func->genericPacks);
TypePackId argTypes = resolveTypePack(funcScope, func->argTypes);
TypePackId retTypes = resolveTypePack(funcScope, func->returnTypes);
TypePackId retTypes = FFlag::LuauStoreReturnTypesAsPackOnAst ? resolveTypePack(funcScope, *func->returnTypes)
: resolveTypePack(funcScope, func->returnTypes_DEPRECATED);
std::vector<TypeId> genericTys;
genericTys.reserve(generics.size());
@ -5855,12 +5858,8 @@ TypeId TypeChecker::resolveTypeWorker(const ScopePtr& scope, const AstType& anno
}
else if (const auto& un = annotation.as<AstTypeUnion>())
{
if (FFlag::LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
{
if (un->types.size == 1)
return resolveType(scope, *un->types.data[0]);
}
if (un->types.size == 1)
return resolveType(scope, *un->types.data[0]);
std::vector<TypeId> types;
for (AstType* ann : un->types)
types.push_back(resolveType(scope, *ann));
@ -5869,12 +5868,8 @@ TypeId TypeChecker::resolveTypeWorker(const ScopePtr& scope, const AstType& anno
}
else if (const auto& un = annotation.as<AstTypeIntersection>())
{
if (FFlag::LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
{
if (un->types.size == 1)
return resolveType(scope, *un->types.data[0]);
}
if (un->types.size == 1)
return resolveType(scope, *un->types.data[0]);
std::vector<TypeId> types;
for (AstType* ann : un->types)
types.push_back(resolveType(scope, *ann));
@ -6481,7 +6476,7 @@ void TypeChecker::resolve(const TypeGuardPredicate& typeguardP, RefinementMap& r
return refine(
[](TypeId ty) -> bool
{
return get<ClassType>(ty);
return get<ExternType>(ty);
}
);
}
@ -6496,13 +6491,13 @@ void TypeChecker::resolve(const TypeGuardPredicate& typeguardP, RefinementMap& r
TypeId type = follow(typeFun->type);
// You cannot refine to the top class type.
if (type == builtinTypes->classType)
if (type == builtinTypes->externType)
{
return addRefinement(refis, typeguardP.lvalue, errorRecoveryType(scope));
}
// We're only interested in the root class of any classes.
if (auto ctv = get<ClassType>(type); !ctv || (ctv->parent != builtinTypes->classType && !hasTag(type, kTypeofRootTag)))
// We're only interested in the root type of any extern type.
if (auto etv = get<ExternType>(type); !etv || (etv->parent != builtinTypes->externType && !hasTag(type, kTypeofRootTag)))
return addRefinement(refis, typeguardP.lvalue, errorRecoveryType(scope));
// This probably hints at breaking out type filtering functions from the predicate solver so that typeof is not tightly coupled with IsA.

View file

@ -208,6 +208,26 @@ TypePackIterator end(TypePackId tp)
return TypePackIterator{};
}
TypePackId getTail(TypePackId tp)
{
DenseHashSet<TypePackId> seen{nullptr};
while (tp)
{
tp = follow(tp);
if (seen.contains(tp))
break;
seen.insert(tp);
if (auto pack = get<TypePack>(tp); pack && pack->tail)
tp = *pack->tail;
else
break;
}
return follow(tp);
}
bool areEqual(SeenSet& seen, const TypePackVar& lhs, const TypePackVar& rhs)
{
TypePackId lhsId = const_cast<TypePackId>(&lhs);

View file

@ -307,9 +307,9 @@ struct TraversalState
prop = &it->second;
}
}
else if (auto c = get<ClassType>(*currentType))
else if (auto c = get<ExternType>(*currentType))
{
prop = lookupClassProp(c, property.name);
prop = lookupExternTypeProp(c, property.name);
}
// For a metatable type, the table takes priority; check that before
// falling through to the metatable entry below.
@ -461,7 +461,7 @@ struct TraversalState
indexer = &(*mtMt->indexer);
}
// Note: we don't appear to walk the class hierarchy for indexers
else if (auto ct = get<ClassType>(current); ct && ct->indexer)
else if (auto ct = get<ExternType>(current); ct && ct->indexer)
indexer = &(*ct->indexer);
if (indexer)

View file

@ -11,9 +11,6 @@
#include <algorithm>
LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTFLAG(LuauAutocompleteRefactorsForIncrementalAutocomplete);
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope);
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
LUAU_FASTFLAG(LuauNonReentrantGeneralization2)
LUAU_FASTFLAG(LuauDisableNewSolverAssertsInMixedMode)
@ -327,11 +324,10 @@ TypePack extendTypePack(
{
FreeType ft{ftp->scope, builtinTypes->neverType, builtinTypes->unknownType, ftp->polarity};
t = arena.addType(ft);
if (FFlag::LuauTrackInteriorFreeTypesOnScope)
trackInteriorFreeType(ftp->scope, t);
trackInteriorFreeType(ftp->scope, t);
}
else
t = FFlag::LuauFreeTypesMustHaveBounds ? arena.freshType(builtinTypes, ftp->scope) : arena.freshType_DEPRECATED(ftp->scope);
t = arena.freshType(builtinTypes, ftp->scope);
}
newPack.head.push_back(t);
@ -438,7 +434,6 @@ TypeId stripNil(NotNull<BuiltinTypes> builtinTypes, TypeArena& arena, TypeId ty)
ErrorSuppression shouldSuppressErrors(NotNull<Normalizer> normalizer, TypeId ty)
{
LUAU_ASSERT(FFlag::LuauSolverV2 || FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete);
std::shared_ptr<const NormalizedType> normType = normalizer->normalize(ty);
if (!normType)
@ -556,10 +551,8 @@ std::vector<TypeId> findBlockedArgTypesIn(AstExprCall* expr, NotNull<DenseHashMa
void trackInteriorFreeType(Scope* scope, TypeId ty)
{
if (FFlag::LuauDisableNewSolverAssertsInMixedMode)
LUAU_ASSERT(FFlag::LuauTrackInteriorFreeTypesOnScope);
else
LUAU_ASSERT(FFlag::LuauSolverV2 && FFlag::LuauTrackInteriorFreeTypesOnScope);
if (!FFlag::LuauDisableNewSolverAssertsInMixedMode)
LUAU_ASSERT(FFlag::LuauSolverV2);
for (; scope; scope = scope->parent.get())
{
if (scope->interiorFreeTypes)

View file

@ -22,7 +22,6 @@ LUAU_FASTFLAGVARIABLE(LuauTransitiveSubtyping)
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAGVARIABLE(LuauFixIndexerSubtypingOrdering)
LUAU_FASTFLAGVARIABLE(LuauUnifierRecursionOnRestart)
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
namespace Luau
{
@ -292,7 +291,7 @@ TypePackId Widen::clean(TypePackId)
bool Widen::ignoreChildren(TypeId ty)
{
if (get<ClassType>(ty))
if (get<ExternType>(ty))
return true;
return !log->is<UnionType>(ty);
@ -693,13 +692,13 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
else if (log.getMutable<MetatableType>(subTy))
tryUnifyWithMetatable(superTy, subTy, /*reversed*/ true);
else if (log.getMutable<ClassType>(superTy))
tryUnifyWithClass(subTy, superTy, /*reversed*/ false);
else if (log.getMutable<ExternType>(superTy))
tryUnifyWithExternType(subTy, superTy, /*reversed*/ false);
// Unification of nonclasses with classes is almost, but not quite symmetrical.
// The order in which we perform this test is significant in the case that both types are classes.
else if (log.getMutable<ClassType>(subTy))
tryUnifyWithClass(subTy, superTy, /*reversed*/ true);
// Unification of Luau types with extern types is almost, but not quite symmetrical.
// The order in which we perform this test is significant in the case that both types are extern types.
else if (log.getMutable<ExternType>(subTy))
tryUnifyWithExternType(subTy, superTy, /*reversed*/ true);
else if (log.get<NegationType>(superTy) || log.get<NegationType>(subTy))
tryUnifyNegations(subTy, superTy);
@ -1107,15 +1106,15 @@ void Unifier::tryUnifyNormalizedTypes(
if (!get<PrimitiveType>(superNorm.errors))
return reportError(location, TypeMismatch{superTy, subTy, reason, error, mismatchContext()});
for (const auto& [subClass, _] : subNorm.classes.classes)
for (const auto& [subExternType, _] : subNorm.externTypes.externTypes)
{
bool found = false;
const ClassType* subCtv = get<ClassType>(subClass);
const ExternType* subCtv = get<ExternType>(subExternType);
LUAU_ASSERT(subCtv);
for (const auto& [superClass, superNegations] : superNorm.classes.classes)
for (const auto& [superExternType, superNegations] : superNorm.externTypes.externTypes)
{
const ClassType* superCtv = get<ClassType>(superClass);
const ExternType* superCtv = get<ExternType>(superExternType);
LUAU_ASSERT(superCtv);
if (isSubclass(subCtv, superCtv))
@ -1124,7 +1123,7 @@ void Unifier::tryUnifyNormalizedTypes(
for (TypeId negation : superNegations)
{
const ClassType* negationCtv = get<ClassType>(negation);
const ExternType* negationCtv = get<ExternType>(negation);
LUAU_ASSERT(negationCtv);
if (isSubclass(subCtv, negationCtv))
@ -1559,7 +1558,7 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal
if (FFlag::LuauSolverV2)
return freshType(NotNull{types}, builtinTypes, scope);
else
return FFlag::LuauFreeTypesMustHaveBounds ? types->freshType(builtinTypes, scope, level) : types->freshType_DEPRECATED(scope, level);
return types->freshType(builtinTypes, scope, level);
};
const TypePackId emptyTp = types->addTypePack(TypePack{{}, std::nullopt});
@ -2382,8 +2381,8 @@ void Unifier::tryUnifyWithMetatable(TypeId subTy, TypeId superTy, bool reversed)
}
}
// Class unification is almost, but not quite symmetrical. We use the 'reversed' boolean to indicate which scenario we are evaluating.
void Unifier::tryUnifyWithClass(TypeId subTy, TypeId superTy, bool reversed)
// Extern type unification is almost, but not quite symmetrical. We use the 'reversed' boolean to indicate which scenario we are evaluating.
void Unifier::tryUnifyWithExternType(TypeId subTy, TypeId superTy, bool reversed)
{
if (reversed)
std::swap(superTy, subTy);
@ -2396,20 +2395,20 @@ void Unifier::tryUnifyWithClass(TypeId subTy, TypeId superTy, bool reversed)
reportError(location, TypeMismatch{subTy, superTy, mismatchContext()});
};
const ClassType* superClass = get<ClassType>(superTy);
if (!superClass)
ice("tryUnifyClass invoked with non-class Type");
const ExternType* superExternType = get<ExternType>(superTy);
if (!superExternType)
ice("tryUnifyExternType invoked with non-class Type");
if (const ClassType* subClass = get<ClassType>(subTy))
if (const ExternType* subExternType = get<ExternType>(subTy))
{
switch (variance)
{
case Covariant:
if (!isSubclass(subClass, superClass))
if (!isSubclass(subExternType, superExternType))
return fail();
return;
case Invariant:
if (subClass != superClass)
if (subExternType != superExternType)
return fail();
return;
}
@ -2434,7 +2433,7 @@ void Unifier::tryUnifyWithClass(TypeId subTy, TypeId superTy, bool reversed)
for (const auto& [propName, prop] : subTable->props)
{
const Property* classProp = lookupClassProp(superClass, propName);
const Property* classProp = lookupExternTypeProp(superExternType, propName);
if (!classProp)
{
ok = false;
@ -2462,7 +2461,7 @@ void Unifier::tryUnifyWithClass(TypeId subTy, TypeId superTy, bool reversed)
if (subTable->indexer)
{
ok = false;
std::string msg = "Class " + superClass->name + " does not have an indexer";
std::string msg = "Extern type " + superExternType->name + " does not have an indexer";
reportError(location, GenericError{msg});
}
@ -2635,9 +2634,9 @@ static void tryUnifyWithAny(
queue.push_back(mt->table);
queue.push_back(mt->metatable);
}
else if (state.log.getMutable<ClassType>(ty))
else if (state.log.getMutable<ExternType>(ty))
{
// ClassTypes never contain free types.
// ExternTypes never contain free types.
}
else if (auto union_ = state.log.getMutable<UnionType>(ty))
queue.insert(queue.end(), union_->options.begin(), union_->options.end());
@ -2654,7 +2653,7 @@ void Unifier::tryUnifyWithAny(TypeId subTy, TypeId anyTy)
LUAU_ASSERT(get<AnyType>(anyTy) || get<ErrorType>(anyTy) || get<UnknownType>(anyTy) || get<NeverType>(anyTy));
// These types are not visited in general loop below
if (log.get<PrimitiveType>(subTy) || log.get<AnyType>(subTy) || log.get<ClassType>(subTy))
if (log.get<PrimitiveType>(subTy) || log.get<AnyType>(subTy) || log.get<ExternType>(subTy))
return;
TypePackId anyTp = types->addTypePack(TypePackVar{VariadicTypePack{anyTy}});

View file

@ -321,9 +321,9 @@ bool Unifier2::unify(TypeId subTy, const FunctionType* superFn)
{
for (TypeId generic : subFn->generics)
{
const GenericType* gen = get<GenericType>(generic);
LUAU_ASSERT(gen);
genericSubstitutions[generic] = freshType(scope, gen->polarity);
const GenericType* gen = get<GenericType>(follow(generic));
if (gen)
genericSubstitutions[generic] = freshType(scope, gen->polarity);
}
for (TypePackId genericPack : subFn->genericPacks)
@ -331,8 +331,8 @@ bool Unifier2::unify(TypeId subTy, const FunctionType* superFn)
if (FFlag::LuauNonReentrantGeneralization2)
{
const GenericTypePack* gen = get<GenericTypePack>(follow(genericPack));
LUAU_ASSERT(gen);
genericPackSubstitutions[genericPack] = freshTypePack(scope, gen->polarity);
if (gen)
genericPackSubstitutions[genericPack] = freshTypePack(scope, gen->polarity);
}
else
genericPackSubstitutions[genericPack] = arena->freshTypePack(scope);

View file

@ -446,7 +446,25 @@ public:
AstStatBlock* body,
size_t functionDepth,
const AstName& debugname,
const std::optional<AstTypeList>& returnAnnotation = {},
AstTypePack* returnAnnotation,
AstTypePack* varargAnnotation = nullptr,
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
);
@ -461,7 +479,9 @@ public:
AstArray<AstGenericTypePack*> genericPacks;
AstLocal* self;
AstArray<AstLocal*> args;
std::optional<AstTypeList> returnAnnotation;
// Clip with FFlagLuauStoreReturnTypesAsPackOnAst
std::optional<AstTypeList> returnAnnotation_DEPRECATED;
AstTypePack* returnAnnotation = nullptr;
bool vararg = false;
Location varargLocation;
AstTypePack* varargAnnotation;
@ -929,6 +949,36 @@ class AstStatDeclareFunction : public AstStat
public:
LUAU_RTTI(AstStatDeclareFunction)
// 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,
AstTypePack* 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,
AstTypePack* retTypes
);
// Clip with FFlagLuauStoreReturnTypesAsPackOnAst
AstStatDeclareFunction(
const Location& location,
const AstName& name,
@ -942,6 +992,7 @@ public:
const AstTypeList& retTypes
);
// Clip with FFlagLuauStoreReturnTypesAsPackOnAst
AstStatDeclareFunction(
const Location& location,
const AstArray<AstAttr*>& attributes,
@ -971,10 +1022,12 @@ public:
AstArray<AstArgumentName> paramNames;
bool vararg = false;
Location varargLocation;
AstTypeList retTypes;
AstTypePack* retTypes;
// Clip with FFlagLuauStoreReturnTypesAsPackOnAst
AstTypeList retTypes_DEPRECATED;
};
struct AstDeclaredClassProp
struct AstDeclaredExternTypeProperty
{
AstName name;
Location nameLocation;
@ -1000,16 +1053,16 @@ struct AstTableIndexer
std::optional<Location> accessLocation;
};
class AstStatDeclareClass : public AstStat
class AstStatDeclareExternType : public AstStat
{
public:
LUAU_RTTI(AstStatDeclareClass)
LUAU_RTTI(AstStatDeclareExternType)
AstStatDeclareClass(
AstStatDeclareExternType(
const Location& location,
const AstName& name,
std::optional<AstName> superName,
const AstArray<AstDeclaredClassProp>& props,
const AstArray<AstDeclaredExternTypeProperty>& props,
AstTableIndexer* indexer = nullptr
);
@ -1018,7 +1071,7 @@ public:
AstName name;
std::optional<AstName> superName;
AstArray<AstDeclaredClassProp> props;
AstArray<AstDeclaredExternTypeProperty> props;
AstTableIndexer* indexer;
};
@ -1095,6 +1148,26 @@ class AstTypeFunction : public AstType
public:
LUAU_RTTI(AstTypeFunction)
AstTypeFunction(
const Location& location,
const AstArray<AstGenericType*>& generics,
const AstArray<AstGenericTypePack*>& genericPacks,
const AstTypeList& argTypes,
const AstArray<std::optional<AstArgumentName>>& argNames,
AstTypePack* returnTypes
);
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,
AstTypePack* returnTypes
);
// Clip with FFlagLuauStoreReturnTypesAsPackOnAst
AstTypeFunction(
const Location& location,
const AstArray<AstGenericType*>& generics,
@ -1104,6 +1177,7 @@ public:
const AstTypeList& returnTypes
);
// Clip with FFlagLuauStoreReturnTypesAsPackOnAst
AstTypeFunction(
const Location& location,
const AstArray<AstAttr*>& attributes,
@ -1124,7 +1198,9 @@ public:
AstArray<AstGenericTypePack*> genericPacks;
AstTypeList argTypes;
AstArray<std::optional<AstArgumentName>> argNames;
AstTypeList returnTypes;
// Clip with FFlagLuauStoreReturnTypesAsPackOnAst
AstTypeList returnTypes_DEPRECATED;
AstTypePack* returnTypes;
};
class AstTypeTypeof : public AstType
@ -1483,7 +1559,7 @@ public:
{
return visit(static_cast<AstStat*>(node));
}
virtual bool visit(class AstStatDeclareClass* node)
virtual bool visit(class AstStatDeclareExternType* node)
{
return visit(static_cast<AstStat*>(node));
}

View file

@ -473,8 +473,10 @@ class CstTypePackExplicit : public CstNode
public:
LUAU_CST_RTTI(CstTypePackExplicit)
CstTypePackExplicit(Position openParenthesesPosition, Position closeParenthesesPosition, AstArray<Position> commaPositions);
explicit CstTypePackExplicit();
explicit CstTypePackExplicit(Position openParenthesesPosition, Position closeParenthesesPosition, AstArray<Position> commaPositions);
bool hasParentheses;
Position openParenthesesPosition;
Position closeParenthesesPosition;
AstArray<Position> commaPositions;

View file

@ -157,8 +157,8 @@ private:
// type function Name ... end
AstStat* parseTypeFunction(const Location& start, bool exported, Position typeKeywordPosition);
AstDeclaredClassProp parseDeclaredClassMethod(const AstArray<AstAttr*>& attributes);
AstDeclaredClassProp parseDeclaredClassMethod_DEPRECATED();
AstDeclaredExternTypeProperty parseDeclaredExternTypeMethod(const AstArray<AstAttr*>& attributes);
AstDeclaredExternTypeProperty parseDeclaredExternTypeMethod_DEPRECATED();
// `declare global' Name: Type |
@ -182,6 +182,14 @@ private:
const Name* localName,
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
void parseExprList(TempVector<AstExpr*>& result, TempVector<Position>* commaPositions = nullptr);
@ -219,8 +227,12 @@ private:
TempVector<std::optional<Position>>* nameColonPositions = nullptr
);
std::optional<AstTypeList> parseOptionalReturnType(Position* returnSpecifierPosition = nullptr);
std::pair<Location, AstTypeList> parseReturnType();
AstTypePack* parseOptionalReturnType(Position* returnSpecifierPosition = nullptr);
AstTypePack* parseReturnType();
// Clip with FFlagLuauStoreReturnTypesAsPackOnAst
std::optional<AstTypeList> parseOptionalReturnType_DEPRECATED(Position* returnSpecifierPosition = nullptr);
std::pair<Location, AstTypeList> parseReturnType_DEPRECATED();
struct TableIndexerResult
{
@ -491,7 +503,7 @@ private:
std::vector<CstTypeTable::Item> scratchCstTableTypeProps;
std::vector<AstType*> scratchType;
std::vector<AstTypeOrPack> scratchTypeOrPack;
std::vector<AstDeclaredClassProp> scratchDeclaredClassProps;
std::vector<AstDeclaredExternTypeProperty> scratchDeclaredClassProps;
std::vector<AstExprTable::Item> scratchItem;
std::vector<CstExprTable::Item> scratchCstItem;
std::vector<AstArgumentName> scratchArgName;

View file

@ -4,6 +4,7 @@
#include "Luau/Common.h"
LUAU_FASTFLAG(LuauDeprecatedAttribute);
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
namespace Luau
{
@ -241,7 +242,7 @@ AstExprFunction::AstExprFunction(
AstStatBlock* body,
size_t functionDepth,
const AstName& debugname,
const std::optional<AstTypeList>& returnAnnotation,
AstTypePack* returnAnnotation,
AstTypePack* varargAnnotation,
const std::optional<Location>& argLocation
)
@ -260,6 +261,41 @@ AstExprFunction::AstExprFunction(
, debugname(debugname)
, 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)
@ -275,8 +311,16 @@ void AstExprFunction::visit(AstVisitor* visitor)
if (varargAnnotation)
varargAnnotation->visit(visitor);
if (returnAnnotation)
visitTypeList(visitor, *returnAnnotation);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
if (returnAnnotation)
returnAnnotation->visit(visitor);
}
else
{
if (returnAnnotation_DEPRECATED)
visitTypeList(visitor, *returnAnnotation_DEPRECATED);
}
body->visit(visitor);
}
@ -855,7 +899,7 @@ AstStatDeclareFunction::AstStatDeclareFunction(
const AstArray<AstArgumentName>& paramNames,
bool vararg,
const Location& varargLocation,
const AstTypeList& retTypes
AstTypePack* retTypes
)
: AstStat(ClassIndex(), location)
, attributes()
@ -869,8 +913,66 @@ AstStatDeclareFunction::AstStatDeclareFunction(
, varargLocation(varargLocation)
, retTypes(retTypes)
{
LUAU_ASSERT(FFlag::LuauStoreReturnTypesAsPackOnAst);
}
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,
AstTypePack* retTypes
)
: AstStat(ClassIndex(), location)
, attributes(attributes)
, name(name)
, nameLocation(nameLocation)
, generics(generics)
, genericPacks(genericPacks)
, params(params)
, paramNames(paramNames)
, vararg(vararg)
, varargLocation(varargLocation)
, 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,
@ -894,8 +996,9 @@ AstStatDeclareFunction::AstStatDeclareFunction(
, paramNames(paramNames)
, vararg(vararg)
, varargLocation(varargLocation)
, retTypes(retTypes)
, retTypes_DEPRECATED(retTypes)
{
LUAU_ASSERT(!FFlag::LuauStoreReturnTypesAsPackOnAst);
}
void AstStatDeclareFunction::visit(AstVisitor* visitor)
@ -903,7 +1006,10 @@ void AstStatDeclareFunction::visit(AstVisitor* visitor)
if (visitor->visit(this))
{
visitTypeList(visitor, params);
visitTypeList(visitor, retTypes);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
retTypes->visit(visitor);
else
visitTypeList(visitor, retTypes_DEPRECATED);
}
}
@ -925,11 +1031,11 @@ bool AstStatDeclareFunction::hasAttribute(AstAttr::Type attributeType) const
return hasAttributeInArray(attributes, attributeType);
}
AstStatDeclareClass::AstStatDeclareClass(
AstStatDeclareExternType::AstStatDeclareExternType(
const Location& location,
const AstName& name,
std::optional<AstName> superName,
const AstArray<AstDeclaredClassProp>& props,
const AstArray<AstDeclaredExternTypeProperty>& props,
AstTableIndexer* indexer
)
: AstStat(ClassIndex(), location)
@ -940,11 +1046,11 @@ AstStatDeclareClass::AstStatDeclareClass(
{
}
void AstStatDeclareClass::visit(AstVisitor* visitor)
void AstStatDeclareExternType::visit(AstVisitor* visitor)
{
if (visitor->visit(this))
{
for (const AstDeclaredClassProp& prop : props)
for (const AstDeclaredExternTypeProperty& prop : props)
prop.ty->visit(visitor);
}
}
@ -1035,7 +1141,7 @@ AstTypeFunction::AstTypeFunction(
const AstArray<AstGenericTypePack*>& genericPacks,
const AstTypeList& argTypes,
const AstArray<std::optional<AstArgumentName>>& argNames,
const AstTypeList& returnTypes
AstTypePack* returnTypes
)
: AstType(ClassIndex(), location)
, attributes()
@ -1045,9 +1151,53 @@ AstTypeFunction::AstTypeFunction(
, argNames(argNames)
, returnTypes(returnTypes)
{
LUAU_ASSERT(FFlag::LuauStoreReturnTypesAsPackOnAst);
LUAU_ASSERT(argNames.size == 0 || argNames.size == argTypes.types.size);
}
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,
AstTypePack* returnTypes
)
: AstType(ClassIndex(), location)
, attributes(attributes)
, generics(generics)
, genericPacks(genericPacks)
, argTypes(argTypes)
, argNames(argNames)
, 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,
@ -1063,8 +1213,9 @@ AstTypeFunction::AstTypeFunction(
, genericPacks(genericPacks)
, argTypes(argTypes)
, argNames(argNames)
, returnTypes(returnTypes)
, returnTypes_DEPRECATED(returnTypes)
{
LUAU_ASSERT(!FFlag::LuauStoreReturnTypesAsPackOnAst);
LUAU_ASSERT(argNames.size == 0 || argNames.size == argTypes.types.size);
}
@ -1073,7 +1224,10 @@ void AstTypeFunction::visit(AstVisitor* visitor)
if (visitor->visit(this))
{
visitTypeList(visitor, argTypes);
visitTypeList(visitor, returnTypes);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
returnTypes->visit(visitor);
else
visitTypeList(visitor, returnTypes_DEPRECATED);
}
}

View file

@ -252,8 +252,18 @@ CstTypeSingletonString::CstTypeSingletonString(AstArray<char> sourceString, CstE
LUAU_ASSERT(quoteStyle != CstExprConstantString::QuotedInterp);
}
CstTypePackExplicit::CstTypePackExplicit()
: CstNode(CstClassIndex())
, hasParentheses(false)
, openParenthesesPosition(Position{0, 0})
, closeParenthesesPosition(Position{0, 0})
, commaPositions({})
{
}
CstTypePackExplicit::CstTypePackExplicit(Position openParenthesesPosition, Position closeParenthesesPosition, AstArray<Position> commaPositions)
: CstNode(CstClassIndex())
, hasParentheses(true)
, openParenthesesPosition(openParenthesesPosition)
, closeParenthesesPosition(closeParenthesesPosition)
, commaPositions(commaPositions)

View file

@ -19,14 +19,13 @@ LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100)
// See docs/SyntaxChanges.md for an explanation.
LUAU_FASTFLAGVARIABLE(LuauSolverV2)
LUAU_FASTFLAGVARIABLE(LuauStoreCSTData2)
LUAU_FASTFLAGVARIABLE(LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
LUAU_FASTFLAGVARIABLE(LuauAstTypeGroup3)
LUAU_FASTFLAGVARIABLE(ParserNoErrorLimit)
LUAU_FASTFLAGVARIABLE(LuauFixDoBlockEndLocation)
LUAU_FASTFLAGVARIABLE(LuauParseOptionalAsNode2)
LUAU_FASTFLAGVARIABLE(LuauDeclareExternType)
LUAU_FASTFLAGVARIABLE(LuauParseStringIndexer)
LUAU_FASTFLAGVARIABLE(LuauTypeFunResultInAutocomplete)
LUAU_FASTFLAGVARIABLE(LuauDeprecatedAttribute)
LUAU_FASTFLAGVARIABLE(LuauStoreReturnTypesAsPackOnAst)
LUAU_FASTFLAGVARIABLE(LuauFixFunctionWithAttributesStartLocation)
LUAU_DYNAMIC_FASTFLAGVARIABLE(DebugLuauReportReturnTypeVariadicWithTypeSuffix, false)
@ -555,7 +554,7 @@ AstStat* Parser::parseDo()
Location endLocation = lexer.current().location;
body->hasEnd = expectMatchEndAndConsume(Lexeme::ReservedEnd, matchDo);
if (FFlag::LuauFixDoBlockEndLocation && body->hasEnd)
if (body->hasEnd)
body->location.end = endLocation.end;
if (FFlag::LuauStoreCSTData2 && options.storeCstData)
@ -788,7 +787,9 @@ AstStat* Parser::parseFunctionStat(const AstArray<AstAttr*>& attributes)
matchRecoveryStopOnToken[Lexeme::ReservedEnd]++;
AstExprFunction* body = parseFunctionBody(hasself, matchFunction, debugname, nullptr, attributes).first;
AstExprFunction* body = FFlag::LuauStoreReturnTypesAsPackOnAst
? parseFunctionBody(hasself, matchFunction, debugname, nullptr, attributes).first
: parseFunctionBody_DEPRECATED(hasself, matchFunction, debugname, nullptr, attributes).first;
matchRecoveryStopOnToken[Lexeme::ReservedEnd]--;
@ -941,7 +942,8 @@ AstStat* Parser::parseLocal(const AstArray<AstAttr*>& attributes)
matchRecoveryStopOnToken[Lexeme::ReservedEnd]++;
auto [body, var] = parseFunctionBody(false, matchFunction, name.name, &name, attributes);
auto [body, var] = FFlag::LuauStoreReturnTypesAsPackOnAst ? parseFunctionBody(false, matchFunction, name.name, &name, attributes)
: parseFunctionBody_DEPRECATED(false, matchFunction, name.name, &name, attributes);
matchRecoveryStopOnToken[Lexeme::ReservedEnd]--;
@ -1111,7 +1113,10 @@ AstStat* Parser::parseTypeFunction(const Location& start, bool exported, Positio
size_t oldTypeFunctionDepth = typeFunctionDepth;
typeFunctionDepth = functionStack.size();
AstExprFunction* body = parseFunctionBody(/* hasself */ false, matchFn, fnName->name, nullptr, AstArray<AstAttr*>({nullptr, 0})).first;
AstExprFunction* body =
FFlag::LuauStoreReturnTypesAsPackOnAst
? parseFunctionBody(/* hasself */ false, matchFn, fnName->name, nullptr, AstArray<AstAttr*>({nullptr, 0})).first
: parseFunctionBody_DEPRECATED(/* hasself */ false, matchFn, fnName->name, nullptr, AstArray<AstAttr*>({nullptr, 0})).first;
typeFunctionDepth = oldTypeFunctionDepth;
@ -1133,7 +1138,7 @@ AstStat* Parser::parseTypeFunction(const Location& start, bool exported, Positio
}
}
AstDeclaredClassProp Parser::parseDeclaredClassMethod(const AstArray<AstAttr*>& attributes)
AstDeclaredExternTypeProperty Parser::parseDeclaredExternTypeMethod(const AstArray<AstAttr*>& attributes)
{
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
@ -1164,7 +1169,18 @@ AstDeclaredClassProp Parser::parseDeclaredClassMethod(const AstArray<AstAttr*>&
expectMatchAndConsume(')', matchParen);
AstTypeList retTypes = parseOptionalReturnType().value_or(AstTypeList{copy<AstType*>(nullptr, 0), nullptr});
AstTypePack* retTypes;
AstTypeList retTypes_DEPRECATED;
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
retTypes = parseOptionalReturnType();
if (!retTypes)
retTypes = allocator.alloc<AstTypePackExplicit>(lexer.current().location, AstTypeList{copy<AstType*>(nullptr, 0), nullptr});
}
else
{
retTypes_DEPRECATED = parseOptionalReturnType_DEPRECATED().value_or(AstTypeList{copy<AstType*>(nullptr, 0), nullptr});
}
Location end = lexer.previousLocation();
TempVector<AstType*> vars(scratchType);
@ -1172,7 +1188,7 @@ AstDeclaredClassProp Parser::parseDeclaredClassMethod(const AstArray<AstAttr*>&
if (args.size() == 0 || args[0].name.name != "self" || args[0].annotation != nullptr)
{
return AstDeclaredClassProp{
return AstDeclaredExternTypeProperty{
fnName.name, fnName.location, reportTypeError(Location(start, end), {}, "'self' must be present as the unannotated first parameter"), true
};
}
@ -1191,14 +1207,25 @@ AstDeclaredClassProp Parser::parseDeclaredClassMethod(const AstArray<AstAttr*>&
if (vararg && !varargAnnotation)
report(start, "All declaration parameters aside from 'self' must be annotated");
AstType* fnType = allocator.alloc<AstTypeFunction>(
Location(start, end), attributes, generics, genericPacks, AstTypeList{copy(vars), varargAnnotation}, copy(varNames), retTypes
);
AstType* fnType =
FFlag::LuauStoreReturnTypesAsPackOnAst
? allocator.alloc<AstTypeFunction>(
Location(start, end), attributes, generics, genericPacks, AstTypeList{copy(vars), varargAnnotation}, copy(varNames), retTypes
)
: allocator.alloc<AstTypeFunction>(
Location(start, end),
attributes,
generics,
genericPacks,
AstTypeList{copy(vars), varargAnnotation},
copy(varNames),
retTypes_DEPRECATED
);
return AstDeclaredClassProp{fnName.name, fnName.location, fnType, true, Location(start, end)};
return AstDeclaredExternTypeProperty{fnName.name, fnName.location, fnType, true, Location(start, end)};
}
AstDeclaredClassProp Parser::parseDeclaredClassMethod_DEPRECATED()
AstDeclaredExternTypeProperty Parser::parseDeclaredExternTypeMethod_DEPRECATED()
{
Location start = lexer.current().location;
@ -1227,7 +1254,18 @@ AstDeclaredClassProp Parser::parseDeclaredClassMethod_DEPRECATED()
expectMatchAndConsume(')', matchParen);
AstTypeList retTypes = parseOptionalReturnType().value_or(AstTypeList{copy<AstType*>(nullptr, 0), nullptr});
AstTypePack* retTypes;
AstTypeList retTypes_DEPRECATED;
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
retTypes = parseOptionalReturnType();
if (!retTypes)
retTypes = allocator.alloc<AstTypePackExplicit>(lexer.current().location, AstTypeList{copy<AstType*>(nullptr, 0), nullptr});
}
else
{
retTypes_DEPRECATED = parseOptionalReturnType_DEPRECATED().value_or(AstTypeList{copy<AstType*>(nullptr, 0), nullptr});
}
Location end = lexer.previousLocation();
TempVector<AstType*> vars(scratchType);
@ -1235,7 +1273,7 @@ AstDeclaredClassProp Parser::parseDeclaredClassMethod_DEPRECATED()
if (args.size() == 0 || args[0].name.name != "self" || args[0].annotation != nullptr)
{
return AstDeclaredClassProp{
return AstDeclaredExternTypeProperty{
fnName.name, fnName.location, reportTypeError(Location(start, end), {}, "'self' must be present as the unannotated first parameter"), true
};
}
@ -1254,11 +1292,16 @@ AstDeclaredClassProp Parser::parseDeclaredClassMethod_DEPRECATED()
if (vararg && !varargAnnotation)
report(start, "All declaration parameters aside from 'self' must be annotated");
AstType* fnType = allocator.alloc<AstTypeFunction>(
Location(start, end), generics, genericPacks, AstTypeList{copy(vars), varargAnnotation}, copy(varNames), retTypes
);
AstType* fnType =
FFlag::LuauStoreReturnTypesAsPackOnAst
? allocator.alloc<AstTypeFunction>(
Location(start, end), generics, genericPacks, AstTypeList{copy(vars), varargAnnotation}, copy(varNames), retTypes
)
: allocator.alloc<AstTypeFunction>(
Location(start, end), generics, genericPacks, AstTypeList{copy(vars), varargAnnotation}, copy(varNames), retTypes_DEPRECATED
);
return AstDeclaredClassProp{fnName.name, fnName.location, fnType, true, Location(start, end)};
return AstDeclaredExternTypeProperty{fnName.name, fnName.location, fnType, true, Location(start, end)};
}
AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*>& attributes)
@ -1296,7 +1339,18 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
expectMatchAndConsume(')', matchParen);
AstTypeList retTypes = parseOptionalReturnType().value_or(AstTypeList{copy<AstType*>(nullptr, 0)});
AstTypePack* retTypes;
AstTypeList retTypes_DEPRECATED;
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
retTypes = parseOptionalReturnType();
if (!retTypes)
retTypes = allocator.alloc<AstTypePackExplicit>(lexer.current().location, AstTypeList{copy<AstType*>(nullptr, 0), nullptr});
}
else
{
retTypes_DEPRECATED = parseOptionalReturnType_DEPRECATED().value_or(AstTypeList{copy<AstType*>(nullptr, 0)});
}
Location end = lexer.current().location;
TempVector<AstType*> vars(scratchType);
@ -1314,34 +1368,77 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
if (vararg && !varargAnnotation)
return reportStatError(Location(start, end), {}, {}, "All declaration parameters must be annotated");
return allocator.alloc<AstStatDeclareFunction>(
Location(start, end),
attributes,
globalName.name,
globalName.location,
generics,
genericPacks,
AstTypeList{copy(vars), varargAnnotation},
copy(varNames),
vararg,
varargLocation,
retTypes
);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
return allocator.alloc<AstStatDeclareFunction>(
Location(start, end),
attributes,
globalName.name,
globalName.location,
generics,
genericPacks,
AstTypeList{copy(vars), varargAnnotation},
copy(varNames),
vararg,
varargLocation,
retTypes
);
}
else
{
return allocator.alloc<AstStatDeclareFunction>(
Location(start, end),
attributes,
globalName.name,
globalName.location,
generics,
genericPacks,
AstTypeList{copy(vars), varargAnnotation},
copy(varNames),
vararg,
varargLocation,
retTypes_DEPRECATED
);
}
}
else if (AstName(lexer.current().name) == "class")
else if (AstName(lexer.current().name) == "class" || (FFlag::LuauDeclareExternType && AstName(lexer.current().name) == "extern"))
{
bool foundExtern = false;
if (FFlag::LuauDeclareExternType)
{
if (AstName(lexer.current().name) == "extern")
{
foundExtern = true;
nextLexeme();
if (AstName(lexer.current().name) != "type")
return reportStatError(lexer.current().location, {}, {}, "Expected `type` keyword after `extern`, but got %s instead", lexer.current().name);
}
}
nextLexeme();
Location classStart = lexer.current().location;
Name className = parseName("class name");
Name className = parseName(FFlag::LuauDeclareExternType ? "type name" : "class name");
std::optional<AstName> superName = std::nullopt;
if (AstName(lexer.current().name) == "extends")
{
nextLexeme();
superName = parseName("superclass name").name;
superName = parseName(FFlag::LuauDeclareExternType ? "supertype name" : "superclass name").name;
}
TempVector<AstDeclaredClassProp> props(scratchDeclaredClassProps);
if (FFlag::LuauDeclareExternType)
{
if (foundExtern)
{
if (AstName(lexer.current().name) != "with")
report(lexer.current().location, "Expected `with` keyword before listing properties of the external type, but got %s instead", lexer.current().name);
else
nextLexeme();
}
}
TempVector<AstDeclaredExternTypeProperty> props(scratchDeclaredClassProps);
AstTableIndexer* indexer = nullptr;
while (lexer.current().type != Lexeme::ReservedEnd)
@ -1368,9 +1465,9 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
if (lexer.current().type == Lexeme::ReservedFunction)
{
if (FFlag::LuauDeprecatedAttribute)
props.push_back(parseDeclaredClassMethod(attributes));
props.push_back(parseDeclaredExternTypeMethod(attributes));
else
props.push_back(parseDeclaredClassMethod_DEPRECATED());
props.push_back(parseDeclaredExternTypeMethod_DEPRECATED());
}
else if (lexer.current().type == '[')
{
@ -1393,7 +1490,7 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
if (chars && !containsNull)
{
props.push_back(AstDeclaredClassProp{
props.push_back(AstDeclaredExternTypeProperty{
AstName(chars->data), Location(nameBegin, nameEnd), type, false, Location(begin.location, lexer.previousLocation())
});
}
@ -1413,7 +1510,10 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
badIndexer = parseTableIndexer_DEPRECATED(AstTableAccess::ReadWrite, std::nullopt, begin);
// we lose all additional indexer expressions from the AST after error recovery here
report(badIndexer->location, "Cannot have more than one class indexer");
if (FFlag::LuauDeclareExternType)
report(badIndexer->location, "Cannot have more than one indexer on an extern type");
else
report(badIndexer->location, "Cannot have more than one class indexer");
}
else
{
@ -1434,7 +1534,7 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
expectAndConsume(':', "property type annotation");
AstType* propType = parseType();
props.push_back(
AstDeclaredClassProp{propName->name, propName->location, propType, false, Location(propStart, lexer.previousLocation())}
AstDeclaredExternTypeProperty{propName->name, propName->location, propType, false, Location(propStart, lexer.previousLocation())}
);
}
}
@ -1444,9 +1544,9 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
if (lexer.current().type == Lexeme::ReservedFunction)
{
if (FFlag::LuauDeprecatedAttribute)
props.push_back(parseDeclaredClassMethod(attributes));
props.push_back(parseDeclaredExternTypeMethod(attributes));
else
props.push_back(parseDeclaredClassMethod_DEPRECATED());
props.push_back(parseDeclaredExternTypeMethod_DEPRECATED());
}
else if (lexer.current().type == '[' &&
(lexer.lookahead().type == Lexeme::RawString || lexer.lookahead().type == Lexeme::QuotedString))
@ -1468,7 +1568,7 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
if (chars && !containsNull)
{
props.push_back(AstDeclaredClassProp{
props.push_back(AstDeclaredExternTypeProperty{
AstName(chars->data), Location(nameBegin, nameEnd), type, false, Location(begin.location, lexer.previousLocation())
});
}
@ -1492,7 +1592,10 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
badIndexer = parseTableIndexer_DEPRECATED(AstTableAccess::ReadWrite, std::nullopt, lexer.current());
// we lose all additional indexer expressions from the AST after error recovery here
report(badIndexer->location, "Cannot have more than one class indexer");
if (FFlag::LuauDeclareExternType)
report(badIndexer->location, "Cannot have more than one indexer on an extern type");
else
report(badIndexer->location, "Cannot have more than one class indexer");
}
else
{
@ -1515,7 +1618,7 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
expectAndConsume(':', "property type annotation");
AstType* propType = parseType();
props.push_back(
AstDeclaredClassProp{propName->name, propName->location, propType, false, Location(propStart, lexer.previousLocation())}
AstDeclaredExternTypeProperty{propName->name, propName->location, propType, false, Location(propStart, lexer.previousLocation())}
);
}
}
@ -1524,7 +1627,7 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
Location classEnd = lexer.current().location;
nextLexeme(); // skip past `end`
return allocator.alloc<AstStatDeclareClass>(Location(classStart, classEnd), className.name, superName, copy(props), indexer);
return allocator.alloc<AstStatDeclareExternType>(Location(classStart, classEnd), className.name, superName, copy(props), indexer);
}
else if (std::optional<Name> globalName = parseNameOpt("global variable name"))
{
@ -1533,6 +1636,10 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
AstType* type = parseType(/* in declaration context */ true);
return allocator.alloc<AstStatDeclareGlobal>(Location(start, type->location), globalName->name, globalName->location, type);
}
else if (FFlag::LuauDeclareExternType)
{
return reportStatError(start, {}, {}, "declare must be followed by an identifier, 'function', or 'extern type'");
}
else
{
return reportStatError(start, {}, {}, "declare must be followed by an identifier, 'function', or 'class'");
@ -1638,6 +1745,8 @@ std::pair<AstExprFunction*, AstLocal*> Parser::parseFunctionBody(
const AstArray<AstAttr*>& attributes
)
{
LUAU_ASSERT(FFlag::LuauStoreReturnTypesAsPackOnAst);
Location start = matchFunction.location;
if (FFlag::LuauFixFunctionWithAttributesStartLocation)
@ -1689,7 +1798,145 @@ std::pair<AstExprFunction*, AstLocal*> Parser::parseFunctionBody(
matchRecoveryStopOnToken[')']--;
std::optional<AstTypeList> typelist = parseOptionalReturnType(cstNode ? &cstNode->returnSpecifierPosition : nullptr);
AstTypePack* typelist = parseOptionalReturnType(cstNode ? &cstNode->returnSpecifierPosition : nullptr);
AstLocal* funLocal = nullptr;
if (localName)
funLocal = pushLocal(Binding(*localName, nullptr));
unsigned int localsBegin = saveLocals();
Function fun;
fun.vararg = vararg;
functionStack.emplace_back(fun);
auto [self, vars] = prepareFunctionArguments(start, hasself, args);
AstStatBlock* body = parseBlock();
functionStack.pop_back();
restoreLocals(localsBegin);
Location end = lexer.current().location;
bool hasEnd = expectMatchEndAndConsume(Lexeme::ReservedEnd, matchFunction);
body->hasEnd = hasEnd;
if (FFlag::LuauStoreCSTData2)
{
AstExprFunction* node = allocator.alloc<AstExprFunction>(
Location(start, end),
attributes,
generics,
genericPacks,
self,
vars,
vararg,
varargLocation,
body,
functionStack.size(),
debugname,
typelist,
varargAnnotation,
argLocation
);
if (options.storeCstData)
{
cstNode->functionKeywordPosition = matchFunction.location.begin;
cstNodeMap[node] = cstNode;
}
return {node, funLocal};
}
else
{
return {
allocator.alloc<AstExprFunction>(
Location(start, end),
attributes,
generics,
genericPacks,
self,
vars,
vararg,
varargLocation,
body,
functionStack.size(),
debugname,
typelist,
varargAnnotation,
argLocation
),
funLocal
};
}
}
std::pair<AstExprFunction*, AstLocal*> Parser::parseFunctionBody_DEPRECATED(
bool hasself,
const Lexeme& matchFunction,
const AstName& debugname,
const Name* localName,
const AstArray<AstAttr*>& attributes
)
{
LUAU_ASSERT(!FFlag::LuauStoreReturnTypesAsPackOnAst);
Location start = matchFunction.location;
if (FFlag::LuauFixFunctionWithAttributesStartLocation)
{
if (attributes.size > 0)
start = attributes.data[0]->location;
}
auto* cstNode = FFlag::LuauStoreCSTData2 && options.storeCstData ? allocator.alloc<CstExprFunction>() : nullptr;
auto [generics, genericPacks] =
FFlag::LuauStoreCSTData2 && cstNode
? parseGenericTypeList(
/* withDefaultValues= */ false, &cstNode->openGenericsPosition, &cstNode->genericsCommaPositions, &cstNode->closeGenericsPosition
)
: parseGenericTypeList(/* withDefaultValues= */ false);
MatchLexeme matchParen = lexer.current();
expectAndConsume('(', "function");
// NOTE: This was added in conjunction with passing `searchForMissing` to
// `expectMatchAndConsume` inside `parseTableType` so that the behavior of
// parsing code like below (note the missing `}`):
//
// function (t: { a: number ) end
//
// ... will still parse as (roughly):
//
// function (t: { a: number }) end
//
matchRecoveryStopOnToken[')']++;
TempVector<Binding> args(scratchBinding);
bool vararg = false;
Location varargLocation;
AstTypePack* varargAnnotation = nullptr;
if (lexer.current().type != ')')
std::tie(vararg, varargLocation, varargAnnotation) =
parseBindingList(args, /* allowDot3= */ true, cstNode ? &cstNode->argsCommaPositions : nullptr);
std::optional<Location> argLocation;
if (matchParen.type == Lexeme::Type('(') && lexer.current().type == Lexeme::Type(')'))
argLocation = Location(matchParen.position, lexer.current().location.end);
expectMatchAndConsume(')', matchParen, true);
matchRecoveryStopOnToken[')']--;
std::optional<AstTypeList> typelist = parseOptionalReturnType_DEPRECATED(cstNode ? &cstNode->returnSpecifierPosition : nullptr);
AstLocal* funLocal = nullptr;
@ -1916,8 +2163,9 @@ AstTypePack* Parser::parseTypeList(
return nullptr;
}
std::optional<AstTypeList> Parser::parseOptionalReturnType(Position* returnSpecifierPosition)
AstTypePack* Parser::parseOptionalReturnType(Position* returnSpecifierPosition)
{
LUAU_ASSERT(FFlag::LuauStoreReturnTypesAsPackOnAst);
if (lexer.current().type == ':' || lexer.current().type == Lexeme::SkinnyArrow)
{
if (lexer.current().type == Lexeme::SkinnyArrow)
@ -1929,7 +2177,41 @@ std::optional<AstTypeList> Parser::parseOptionalReturnType(Position* returnSpeci
unsigned int oldRecursionCount = recursionCounter;
auto [_location, result] = parseReturnType();
auto result = parseReturnType();
LUAU_ASSERT(result);
// At this point, if we find a , character, it indicates that there are multiple return types
// in this type annotation, but the list wasn't wrapped in parentheses.
if (lexer.current().type == ',')
{
report(lexer.current().location, "Expected a statement, got ','; did you forget to wrap the list of return types in parentheses?");
nextLexeme();
}
recursionCounter = oldRecursionCount;
return result;
}
return nullptr;
}
std::optional<AstTypeList> Parser::parseOptionalReturnType_DEPRECATED(Luau::Position* returnSpecifierPosition)
{
LUAU_ASSERT(!FFlag::LuauStoreReturnTypesAsPackOnAst);
if (lexer.current().type == ':' || lexer.current().type == Lexeme::SkinnyArrow)
{
if (lexer.current().type == Lexeme::SkinnyArrow)
report(lexer.current().location, "Function return type annotations are written after ':' instead of '->'");
if (FFlag::LuauStoreCSTData2 && returnSpecifierPosition)
*returnSpecifierPosition = lexer.current().location.begin;
nextLexeme();
unsigned int oldRecursionCount = recursionCounter;
auto [_location, result] = parseReturnType_DEPRECATED();
// At this point, if we find a , character, it indicates that there are multiple return types
// in this type annotation, but the list wasn't wrapped in parentheses.
@ -1949,8 +2231,119 @@ std::optional<AstTypeList> Parser::parseOptionalReturnType(Position* returnSpeci
}
// ReturnType ::= Type | `(' TypeList `)'
std::pair<Location, AstTypeList> Parser::parseReturnType()
AstTypePack* Parser::parseReturnType()
{
LUAU_ASSERT(FFlag::LuauStoreReturnTypesAsPackOnAst);
incrementRecursionCounter("type annotation");
Lexeme begin = lexer.current();
if (lexer.current().type != '(')
{
if (shouldParseTypePack(lexer))
{
return parseTypePack();
}
else
{
AstType* type = parseType();
AstTypePackExplicit* node = allocator.alloc<AstTypePackExplicit>(type->location, AstTypeList{copy(&type, 1), nullptr});
if (options.storeCstData)
cstNodeMap[node] = allocator.alloc<CstTypePackExplicit>();
return node;
}
}
nextLexeme();
Location innerBegin = lexer.current().location;
matchRecoveryStopOnToken[Lexeme::SkinnyArrow]++;
TempVector<AstType*> result(scratchType);
TempVector<std::optional<AstArgumentName>> resultNames(scratchOptArgName);
TempVector<Position> commaPositions(scratchPosition);
AstTypePack* varargAnnotation = nullptr;
// possibly () -> ReturnType
if (lexer.current().type != ')')
{
if (options.storeCstData)
varargAnnotation = parseTypeList(result, resultNames, &commaPositions);
else
varargAnnotation = parseTypeList(result, resultNames);
}
const Location location{begin.location, lexer.current().location};
Position closeParenthesesPosition = lexer.current().location.begin;
expectMatchAndConsume(')', begin, true);
matchRecoveryStopOnToken[Lexeme::SkinnyArrow]--;
if (lexer.current().type != Lexeme::SkinnyArrow && resultNames.empty())
{
// If it turns out that it's just '(A)', it's possible that there are unions/intersections to follow, so fold over it.
if (FFlag::LuauAstTypeGroup3)
{
if (result.size() == 1)
{
// TODO(CLI-140667): stop parsing type suffix when varargAnnotation != nullptr - this should be a parse error
AstType* inner = varargAnnotation == nullptr ? allocator.alloc<AstTypeGroup>(location, result[0]) : result[0];
AstType* returnType = parseTypeSuffix(inner, begin.location);
if (DFFlag::DebugLuauReportReturnTypeVariadicWithTypeSuffix && varargAnnotation != nullptr &&
(returnType->is<AstTypeUnion>() || returnType->is<AstTypeIntersection>()))
luau_telemetry_parsed_return_type_variadic_with_type_suffix = true;
// If parseType parses nothing, then returnType->location.end only points at the last non-type-pack
// type to successfully parse. We need the span of the whole annotation.
Position endPos = result.size() == 1 ? location.end : returnType->location.end;
AstTypePackExplicit* node = allocator.alloc<AstTypePackExplicit>(Location{location.begin, endPos}, AstTypeList{copy(&returnType, 1), varargAnnotation});
if (options.storeCstData)
cstNodeMap[node] = allocator.alloc<CstTypePackExplicit>();
return node;
}
}
else
{
if (result.size() == 1)
{
AstType* returnType = parseTypeSuffix(result[0], innerBegin);
if (DFFlag::DebugLuauReportReturnTypeVariadicWithTypeSuffix && varargAnnotation != nullptr &&
(returnType->is<AstTypeUnion>() || returnType->is<AstTypeIntersection>()))
luau_telemetry_parsed_return_type_variadic_with_type_suffix = true;
// If parseType parses nothing, then returnType->location.end only points at the last non-type-pack
// type to successfully parse. We need the span of the whole annotation.
Position endPos = result.size() == 1 ? location.end : returnType->location.end;
AstTypePackExplicit* node = allocator.alloc<AstTypePackExplicit>(Location{location.begin, endPos}, AstTypeList{copy(&returnType, 1), varargAnnotation});
if (options.storeCstData)
cstNodeMap[node] = allocator.alloc<CstTypePackExplicit>();
return node;
}
}
AstTypePackExplicit* node = allocator.alloc<AstTypePackExplicit>(location, AstTypeList{copy(result), varargAnnotation});
if (options.storeCstData)
cstNodeMap[node] = allocator.alloc<CstTypePackExplicit>(location.begin, closeParenthesesPosition, copy(commaPositions));
return node;
}
AstType* tail = parseFunctionTypeTail(begin, {nullptr, 0}, {}, {}, copy(result), copy(resultNames), varargAnnotation);
AstTypePackExplicit* node = allocator.alloc<AstTypePackExplicit>(Location{location, tail->location}, AstTypeList{copy(&tail, 1), nullptr});
if (options.storeCstData)
cstNodeMap[node] = allocator.alloc<CstTypePackExplicit>();
return node;
}
// ReturnType ::= Type | `(' TypeList `)'
std::pair<Location, AstTypeList> Parser::parseReturnType_DEPRECATED()
{
LUAU_ASSERT(!FFlag::LuauStoreReturnTypesAsPackOnAst);
incrementRecursionCounter("type annotation");
Lexeme begin = lexer.current();
@ -2574,12 +2967,25 @@ AstType* Parser::parseFunctionTypeTail(
expectAndConsume(Lexeme::SkinnyArrow, "function type");
}
auto [endLocation, returnTypeList] = parseReturnType();
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
auto returnType = parseReturnType();
LUAU_ASSERT(returnType);
AstTypeList paramTypes = AstTypeList{params, varargAnnotation};
return allocator.alloc<AstTypeFunction>(
Location(begin.location, endLocation), attributes, generics, genericPacks, paramTypes, paramNames, returnTypeList
);
AstTypeList paramTypes = AstTypeList{params, varargAnnotation};
return allocator.alloc<AstTypeFunction>(
Location(begin.location, returnType->location), attributes, generics, genericPacks, paramTypes, paramNames, returnType
);
}
else
{
auto [endLocation, returnTypeList] = parseReturnType_DEPRECATED();
AstTypeList paramTypes = AstTypeList{params, varargAnnotation};
return allocator.alloc<AstTypeFunction>(
Location(begin.location, endLocation), attributes, generics, genericPacks, paramTypes, paramNames, returnTypeList
);
}
}
static bool isTypeFollow(Lexeme::Type c)
@ -2693,17 +3099,8 @@ AstType* Parser::parseTypeSuffix(AstType* type, const Location& begin)
}
}
if (FFlag::LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
{
if (parts.size() == 1 && !isUnion && !isIntersection)
return parts[0];
}
else
{
if (parts.size() == 1)
return parts[0];
}
if (parts.size() == 1 && !isUnion && !isIntersection)
return parts[0];
if (isUnion && isIntersection)
{
return reportTypeError(
@ -3536,7 +3933,10 @@ AstExpr* Parser::parseSimpleExpr()
Lexeme matchFunction = lexer.current();
nextLexeme();
return parseFunctionBody(false, matchFunction, AstName(), nullptr, attributes).first;
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
return parseFunctionBody(false, matchFunction, AstName(), nullptr, attributes).first;
else
return parseFunctionBody_DEPRECATED(false, matchFunction, AstName(), nullptr, attributes).first;
}
else if (lexer.current().type == Lexeme::Number)
{
@ -4666,7 +5066,7 @@ void Parser::report(const Location& location, const char* format, va_list args)
parseErrors.emplace_back(location, message);
if (parseErrors.size() >= unsigned(FInt::LuauParseErrorLimit) && (!FFlag::ParserNoErrorLimit || !options.noErrorLimit))
if (parseErrors.size() >= unsigned(FInt::LuauParseErrorLimit) && !options.noErrorLimit)
ParseError::raise(location, "Reached error limit (%d)", int(FInt::LuauParseErrorLimit));
}

View file

@ -131,7 +131,7 @@ static luarequire_WriteResult get_config(lua_State* L, void* ctx, char* buffer,
return write(getFileContents(req->absPath, "/.luaurc"), buffer, buffer_size, size_out);
}
static int load(lua_State* L, void* ctx, const char* chunkname, const char* contents)
static int load(lua_State* L, void* ctx, const char* path, const char* chunkname, const char* contents)
{
ReplRequirer* req = static_cast<ReplRequirer*>(ctx);

View file

@ -28,6 +28,8 @@ LUAU_FASTINTVARIABLE(LuauCompileInlineDepth, 5)
LUAU_FASTFLAGVARIABLE(LuauSeparateCompilerTypeInfo)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
namespace Luau
{
@ -4317,26 +4319,54 @@ void compileOrThrow(BytecodeBuilder& bytecode, const ParseResult& parseResult, c
mainFlags |= LPF_NATIVE_FUNCTION;
}
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()
);
uint32_t mainid = compiler.compileFunction(&main, mainFlags);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
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= */ nullptr
);
uint32_t mainid = compiler.compileFunction(&main, mainFlags);
const Compiler::Function* mainf = compiler.functions.find(&main);
LUAU_ASSERT(mainf && mainf->upvals.empty());
const Compiler::Function* mainf = compiler.functions.find(&main);
LUAU_ASSERT(mainf && mainf->upvals.empty());
bytecode.setMainFunction(mainid);
bytecode.finalize();
bytecode.setMainFunction(mainid);
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)

View file

@ -3,7 +3,6 @@
#include "Luau/Config.h"
#include <functional>
#include <optional>
#include <string>
#include <string_view>
@ -88,6 +87,11 @@ private:
[[nodiscard]] Error navigateToAlias(const std::string& alias, const std::string& value);
[[nodiscard]] Error navigateToAndPopulateConfig(const std::string& desiredAlias);
[[nodiscard]] Error resetToRequirer();
[[nodiscard]] Error jumpToAlias(const std::string& aliasPath);
[[nodiscard]] Error navigateToParent(std::optional<std::string> previousComponent);
[[nodiscard]] Error navigateToChild(const std::string& component);
NavigationContext& navigationContext;
ErrorHandler& errorHandler;
Luau::Config config;

View file

@ -10,24 +10,11 @@
#include <optional>
#include <utility>
static constexpr char kRequireErrorAmbiguous[] = "require path could not be resolved to a unique file";
static constexpr char kRequireErrorGeneric[] = "error requiring module";
namespace Luau::Require
{
using Error = std::optional<std::string>;
static Error toError(NavigationContext::NavigateResult result)
{
if (result == NavigationContext::NavigateResult::Success)
return std::nullopt;
if (result == NavigationContext::NavigateResult::Ambiguous)
return kRequireErrorAmbiguous;
else
return kRequireErrorGeneric;
}
static std::string extractAlias(std::string_view path)
{
// To ignore the '@' alias prefix when processing the alias
@ -53,7 +40,7 @@ Navigator::Status Navigator::navigate(std::string path)
{
std::replace(path.begin(), path.end(), '\\', '/');
if (Error error = toError(navigationContext.reset(navigationContext.getRequirerIdentifier())))
if (Error error = resetToRequirer())
{
errorHandler.reportError(*error);
return Status::ErrorReported;
@ -98,7 +85,7 @@ Error Navigator::navigateImpl(std::string_view path)
// If the alias is "@self", we reset to the requirer's context and
// navigate directly from there.
if (Error error = toError(navigationContext.reset(navigationContext.getRequirerIdentifier())))
if (Error error = resetToRequirer())
return error;
if (Error error = navigateThroughPath(path))
return error;
@ -114,7 +101,7 @@ Error Navigator::navigateImpl(std::string_view path)
if (pathType == PathType::RelativeToCurrent || pathType == PathType::RelativeToParent)
{
if (Error error = toError(navigationContext.toParent()))
if (Error error = navigateToParent(std::nullopt))
return error;
if (Error error = navigateThroughPath(path))
return error;
@ -133,6 +120,7 @@ Error Navigator::navigateThroughPath(std::string_view path)
components = splitPath(components.second);
}
std::optional<std::string> previousComponent;
while (!(components.first.empty() && components.second.empty()))
{
if (components.first == "." || components.first.empty())
@ -142,14 +130,15 @@ Error Navigator::navigateThroughPath(std::string_view path)
}
else if (components.first == "..")
{
if (Error error = toError(navigationContext.toParent()))
if (Error error = navigateToParent(previousComponent))
return error;
}
else
{
if (Error error = toError(navigationContext.toChild(std::string{components.first})))
if (Error error = navigateToChild(std::string{components.first}))
return error;
}
previousComponent = components.first;
components = splitPath(components.second);
}
@ -167,11 +156,11 @@ Error Navigator::navigateToAlias(const std::string& alias, const std::string& va
}
else if (pathType == PathType::Aliased)
{
return "@" + alias + " cannot point to other aliases";
return "alias \"@" + alias + "\" cannot point to an aliased path (\"" + value + "\")";
}
else
{
if (Error error = toError(navigationContext.jumpToAlias(value)))
if (Error error = jumpToAlias(value))
return error;
}
@ -189,7 +178,7 @@ Error Navigator::navigateToAndPopulateConfig(const std::string& desiredAlias)
{
std::optional<std::string> configContents = navigationContext.getConfig();
if (!configContents)
return "could not get configuration file contents";
return "could not get configuration file contents to resolve alias \"" + desiredAlias + "\"";
Luau::ConfigOptions opts;
Luau::ConfigOptions::AliasOptions aliasOpts;
@ -205,4 +194,56 @@ Error Navigator::navigateToAndPopulateConfig(const std::string& desiredAlias)
return std::nullopt;
}
Error Navigator::resetToRequirer()
{
NavigationContext::NavigateResult result = navigationContext.reset(navigationContext.getRequirerIdentifier());
if (result == NavigationContext::NavigateResult::Success)
return std::nullopt;
std::string errorMessage = "could not reset to requiring context";
if (result == NavigationContext::NavigateResult::Ambiguous)
errorMessage += " (ambiguous)";
return errorMessage;
}
Error Navigator::jumpToAlias(const std::string& aliasPath)
{
NavigationContext::NavigateResult result = navigationContext.jumpToAlias(aliasPath);
if (result == NavigationContext::NavigateResult::Success)
return std::nullopt;
std::string errorMessage = "could not jump to alias \"" + aliasPath + "\"";
if (result == NavigationContext::NavigateResult::Ambiguous)
errorMessage += " (ambiguous)";
return errorMessage;
}
Error Navigator::navigateToParent(std::optional<std::string> previousComponent)
{
NavigationContext::NavigateResult result = navigationContext.toParent();
if (result == NavigationContext::NavigateResult::Success)
return std::nullopt;
std::string errorMessage;
if (previousComponent)
errorMessage = "could not get parent of component \"" + *previousComponent + "\"";
else
errorMessage = "could not get parent of requiring context";
if (result == NavigationContext::NavigateResult::Ambiguous)
errorMessage += " (ambiguous)";
return errorMessage;
}
Error Navigator::navigateToChild(const std::string& component)
{
NavigationContext::NavigateResult result = navigationContext.toChild(component);
if (result == NavigationContext::NavigateResult::Success)
return std::nullopt;
std::string errorMessage = "could not resolve child component \"" + component + "\"";
if (result == NavigationContext::NavigateResult::Ambiguous)
errorMessage += " (ambiguous)";
return errorMessage;
}
} // namespace Luau::Require

View file

@ -106,8 +106,10 @@ struct luarequire_Configuration
luarequire_WriteResult (*get_config)(lua_State* L, void* ctx, char* buffer, size_t buffer_size, size_t* size_out);
// Executes the module and places the result on the stack. Returns the
// number of results placed on the stack.
int (*load)(lua_State* L, void* ctx, const char* chunkname, const char* contents);
// number of results placed on the stack. Returning -1 directs the requiring
// thread to yield. In this case, this thread should be resumed with the
// module result pushed onto its stack.
int (*load)(lua_State* L, void* ctx, const char* path, const char* chunkname, const char* contents);
};
// Populates function pointers in the given luarequire_Configuration.
@ -115,7 +117,25 @@ typedef void (*luarequire_Configuration_init)(luarequire_Configuration* config);
// Initializes and pushes the require closure onto the stack without
// registration.
LUALIB_API int lua_pushrequire(lua_State* L, luarequire_Configuration_init config_init, void* ctx);
LUALIB_API int luarequire_pushrequire(lua_State* L, luarequire_Configuration_init config_init, void* ctx);
// Initializes the require library and registers it globally.
LUALIB_API void luaopen_require(lua_State* L, luarequire_Configuration_init config_init, void* ctx);
// Initializes and pushes a "proxyrequire" closure onto the stack. This function
// takes two parameters: the string path to resolve and the chunkname of an
// existing module. The path is resolved as if it were being required from the
// module that the chunkname represents.
LUALIB_API int luarequire_pushproxyrequire(lua_State* L, luarequire_Configuration_init config_init, void* ctx);
// Registers an aliased require path to a result. After registration, the given
// result will always be immediately returned when the given path is required.
// Expects the path and table to be passed as arguments on the stack.
LUALIB_API int luarequire_registermodule(lua_State* L);
// Clears the entry associated with the given cache key from the require cache.
// Expects the cache key to be passed as an argument on the stack.
LUALIB_API int luarequire_clearcacheentry(lua_State* L);
// Clears all entries from the require cache.
LUALIB_API int luarequire_clearcache(lua_State* L);

View file

@ -6,6 +6,8 @@
#include "lua.h"
#include "lualib.h"
#include <string>
static constexpr size_t initalFileBufferSize = 1024;
static constexpr size_t initalIdentifierBufferSize = 64;
@ -111,14 +113,16 @@ std::optional<std::string> RuntimeNavigationContext::getStringFromCWriter(
}
RuntimeErrorHandler::RuntimeErrorHandler(lua_State* L)
RuntimeErrorHandler::RuntimeErrorHandler(lua_State* L, std::string requiredPath)
: L(L)
, errorPrefix("error requiring module \"" + std::move(requiredPath) + "\": ")
{
}
void RuntimeErrorHandler::reportError(std::string message)
{
luaL_errorL(L, "%s", message.c_str());
std::string fullError = errorPrefix + std::move(message);
luaL_errorL(L, "%s", fullError.c_str());
}
} // namespace Luau::Require

View file

@ -4,6 +4,8 @@
#include "Luau/RequireNavigator.h"
#include "Luau/Require.h"
#include <string>
struct lua_State;
struct luarequire_Configuration;
@ -48,11 +50,12 @@ private:
class RuntimeErrorHandler : public ErrorHandler
{
public:
RuntimeErrorHandler(lua_State* L);
RuntimeErrorHandler(lua_State* L, std::string requiredPath);
void reportError(std::string message) override;
private:
lua_State* L;
std::string errorPrefix;
};
} // namespace Luau::Require

View file

@ -35,7 +35,13 @@ static void validateConfig(lua_State* L, const luarequire_Configuration& config)
luaL_error(L, "require configuration is missing required function pointer: load");
}
int lua_pushrequire(lua_State* L, luarequire_Configuration_init config_init, void* ctx)
static int pushrequireclosureinternal(
lua_State* L,
luarequire_Configuration_init config_init,
void* ctx,
lua_CFunction requirelikefunc,
const char* debugname
)
{
luarequire_Configuration* config = static_cast<luarequire_Configuration*>(lua_newuserdata(L, sizeof(luarequire_Configuration)));
if (!config)
@ -46,13 +52,38 @@ int lua_pushrequire(lua_State* L, luarequire_Configuration_init config_init, voi
lua_pushlightuserdata(L, ctx);
// "require" captures config and ctx as upvalues
lua_pushcclosure(L, Luau::Require::lua_require, "require", 2);
// require-like closure captures config and ctx as upvalues
lua_pushcclosurek(L, requirelikefunc, debugname, 2, Luau::Require::lua_requirecont);
return 1;
}
int luarequire_pushrequire(lua_State* L, luarequire_Configuration_init config_init, void* ctx)
{
return pushrequireclosureinternal(L, config_init, ctx, Luau::Require::lua_require, "require");
}
void luaopen_require(lua_State* L, luarequire_Configuration_init config_init, void* ctx)
{
lua_pushrequire(L, config_init, ctx);
luarequire_pushrequire(L, config_init, ctx);
lua_setglobal(L, "require");
}
int luarequire_pushproxyrequire(lua_State* L, luarequire_Configuration_init config_init, void* ctx)
{
return pushrequireclosureinternal(L, config_init, ctx, Luau::Require::lua_proxyrequire, "proxyrequire");
}
int luarequire_registermodule(lua_State* L)
{
return Luau::Require::registerModuleImpl(L);
}
int luarequire_clearcacheentry(lua_State* L)
{
return Luau::Require::clearCacheEntry(L);
}
int luarequire_clearcache(lua_State* L)
{
return Luau::Require::clearCache(L);
}

View file

@ -13,7 +13,11 @@
namespace Luau::Require
{
static const char* cacheTableKey = "_MODULES";
// Stores explicitly registered modules.
static const char* registeredCacheTableKey = "_REGISTEREDMODULES";
// Stores the results of require calls.
static const char* requiredCacheTableKey = "_MODULES";
struct ResolvedRequire
{
@ -32,7 +36,7 @@ struct ResolvedRequire
static bool isCached(lua_State* L, const std::string& key)
{
luaL_findtable(L, LUA_REGISTRYINDEX, cacheTableKey, 1);
luaL_findtable(L, LUA_REGISTRYINDEX, requiredCacheTableKey, 1);
lua_getfield(L, -1, key.c_str());
bool cached = !lua_isnil(L, -1);
lua_pop(L, 2);
@ -40,16 +44,13 @@ static bool isCached(lua_State* L, const std::string& key)
return cached;
}
static ResolvedRequire resolveRequire(luarequire_Configuration* lrc, lua_State* L, void* ctx, std::string path)
static ResolvedRequire resolveRequire(luarequire_Configuration* lrc, lua_State* L, void* ctx, const char* requirerChunkname, std::string path)
{
lua_Debug ar;
lua_getinfo(L, 1, "s", &ar);
if (!lrc->is_require_allowed(L, ctx, ar.source))
if (!lrc->is_require_allowed(L, ctx, requirerChunkname))
luaL_error(L, "require is not supported in this context");
RuntimeNavigationContext navigationContext{lrc, L, ctx, ar.source};
RuntimeErrorHandler errorHandler{L}; // Errors reported directly to lua_State.
RuntimeNavigationContext navigationContext{lrc, L, ctx, requirerChunkname};
RuntimeErrorHandler errorHandler{L, path}; // Errors reported directly to lua_State.
Navigator navigator(navigationContext, errorHandler);
@ -60,7 +61,7 @@ static ResolvedRequire resolveRequire(luarequire_Configuration* lrc, lua_State*
if (!navigationContext.isModulePresent())
{
luaL_errorL(L, "no module present at resolved path");
errorHandler.reportError("no module present at resolved path");
return ResolvedRequire{ResolvedRequire::Status::ErrorReported};
}
@ -74,7 +75,7 @@ static ResolvedRequire resolveRequire(luarequire_Configuration* lrc, lua_State*
if (isCached(L, *cacheKey))
{
// Put cached result on top of stack before returning.
lua_getfield(L, LUA_REGISTRYINDEX, cacheTableKey);
lua_getfield(L, LUA_REGISTRYINDEX, requiredCacheTableKey);
lua_getfield(L, -1, cacheKey->c_str());
lua_remove(L, -2);
@ -103,21 +104,27 @@ static ResolvedRequire resolveRequire(luarequire_Configuration* lrc, lua_State*
};
}
int lua_require(lua_State* L)
static int checkRegisteredModules(lua_State* L, const char* path)
{
luarequire_Configuration* lrc = static_cast<luarequire_Configuration*>(lua_touserdata(L, lua_upvalueindex(1)));
if (!lrc)
luaL_error(L, "unable to find require configuration");
luaL_findtable(L, LUA_REGISTRYINDEX, registeredCacheTableKey, 1);
lua_getfield(L, -1, path);
if (lua_isnil(L, -1))
{
lua_pop(L, 2);
return 0;
}
void* ctx = lua_tolightuserdata(L, lua_upvalueindex(2));
lua_remove(L, -2);
return 1;
}
const char* path = luaL_checkstring(L, 1);
int lua_requirecont(lua_State* L, int status)
{
// Number of stack arguments present before this continuation is called.
const int numStackArgs = 2;
const int numResults = lua_gettop(L) - numStackArgs;
const char* cacheKey = luaL_checkstring(L, 2);
ResolvedRequire resolvedRequire = resolveRequire(lrc, L, ctx, path);
if (resolvedRequire.status == ResolvedRequire::Status::Cached)
return 1;
int numResults = lrc->load(L, ctx, resolvedRequire.chunkname.c_str(), resolvedRequire.contents.c_str());
if (numResults > 1)
luaL_error(L, "module must return a single value");
@ -127,13 +134,13 @@ int lua_require(lua_State* L)
// Initial stack state
// (-1) result
lua_getfield(L, LUA_REGISTRYINDEX, cacheTableKey);
lua_getfield(L, LUA_REGISTRYINDEX, requiredCacheTableKey);
// (-2) result, (-1) cache table
lua_pushvalue(L, -2);
// (-3) result, (-2) cache table, (-1) result
lua_setfield(L, -2, resolvedRequire.cacheKey.c_str());
lua_setfield(L, -2, cacheKey);
// (-2) result, (-1) cache table
lua_pop(L, 1);
@ -143,4 +150,103 @@ int lua_require(lua_State* L)
return numResults;
}
int lua_requireinternal(lua_State* L, const char* requirerChunkname)
{
// If modifying the state of the stack, please update numStackArgs in the
// lua_requirecont continuation function.
luarequire_Configuration* lrc = static_cast<luarequire_Configuration*>(lua_touserdata(L, lua_upvalueindex(1)));
if (!lrc)
luaL_error(L, "unable to find require configuration");
void* ctx = lua_tolightuserdata(L, lua_upvalueindex(2));
// (1) path
const char* path = luaL_checkstring(L, 1);
if (checkRegisteredModules(L, path) == 1)
return 1;
ResolvedRequire resolvedRequire = resolveRequire(lrc, L, ctx, requirerChunkname, path);
if (resolvedRequire.status == ResolvedRequire::Status::Cached)
return 1;
// (1) path, (2) cacheKey
lua_pushstring(L, resolvedRequire.cacheKey.c_str());
int numArgsBeforeLoad = lua_gettop(L);
int numResults = lrc->load(L, ctx, path, resolvedRequire.chunkname.c_str(), resolvedRequire.contents.c_str());
if (numResults == -1)
{
if (lua_gettop(L) != numArgsBeforeLoad)
luaL_error(L, "stack cannot be modified when require yields");
return lua_yield(L, 0);
}
return lua_requirecont(L, LUA_OK);
}
int lua_proxyrequire(lua_State* L)
{
const char* requirerChunkname = luaL_checkstring(L, 2);
return lua_requireinternal(L, requirerChunkname);
}
int lua_require(lua_State* L)
{
lua_Debug ar;
int level = 1;
do
{
if (!lua_getinfo(L, level++, "s", &ar))
luaL_error(L, "require is not supported in this context");
} while (ar.what[0] == 'C');
return lua_requireinternal(L, ar.source);
}
int registerModuleImpl(lua_State* L)
{
if (lua_gettop(L) != 2)
luaL_error(L, "expected 2 arguments: aliased require path and desired result");
size_t len;
const char* path = luaL_checklstring(L, 1, &len);
std::string_view pathView(path, len);
if (pathView.empty() || pathView[0] != '@')
luaL_argerrorL(L, 1, "path must begin with '@'");
luaL_findtable(L, LUA_REGISTRYINDEX, registeredCacheTableKey, 1);
// (1) path, (2) result, (3) cache table
lua_insert(L, 1);
// (1) cache table, (2) path, (3) result
lua_settable(L, 1);
// (1) cache table
lua_pop(L, 1);
return 0;
}
int clearCacheEntry(lua_State* L)
{
const char* cacheKey = luaL_checkstring(L, 1);
luaL_findtable(L, LUA_REGISTRYINDEX, requiredCacheTableKey, 1);
lua_pushnil(L);
lua_setfield(L, -2, cacheKey);
lua_pop(L, 1);
return 0;
}
int clearCache(lua_State* L)
{
lua_newtable(L);
lua_setfield(L, LUA_REGISTRYINDEX, requiredCacheTableKey);
return 0;
}
} // namespace Luau::Require

View file

@ -7,5 +7,12 @@ namespace Luau::Require
{
int lua_require(lua_State* L);
int lua_proxyrequire(lua_State* L);
int lua_requirecont(lua_State* L, int status);
int registerModuleImpl(lua_State* L);
int clearCacheEntry(lua_State* L);
int clearCache(lua_State* L);
} // namespace Luau::Require

View file

@ -179,6 +179,7 @@ target_sources(Luau.Analysis PRIVATE
Analysis/include/Luau/Clone.h
Analysis/include/Luau/Constraint.h
Analysis/include/Luau/ConstraintGenerator.h
Analysis/include/Luau/ConstraintSet.h
Analysis/include/Luau/ConstraintSolver.h
Analysis/include/Luau/ControlFlow.h
Analysis/include/Luau/DataFlowGraph.h

View file

@ -58,6 +58,9 @@ LUALIB_API const char* luaL_findtable(lua_State* L, int idx, const char* fname,
LUALIB_API const char* luaL_typename(lua_State* L, int idx);
// wrapper for making calls from yieldable C functions
LUALIB_API int luaL_callyieldable(lua_State* L, int nargs, int nresults);
/*
** ===============================================================
** some useful macros

View file

@ -12,6 +12,7 @@
#include <string.h>
LUAU_FASTFLAGVARIABLE(LuauLibWhereErrorAutoreserve)
LUAU_FASTFLAG(LuauYieldableContinuations)
// convert a stack index to positive
#define abs_index(L, i) ((i) > 0 || (i) <= LUA_REGISTRYINDEX ? (i) : lua_gettop(L) + (i) + 1)
@ -355,6 +356,22 @@ const char* luaL_typename(lua_State* L, int idx)
return obj ? luaT_objtypename(L, obj) : "no value";
}
int luaL_callyieldable(lua_State* L, int nargs, int nresults)
{
LUAU_ASSERT(FFlag::LuauYieldableContinuations);
api_check(L, iscfunction(L->ci->func));
Closure* cl = clvalue(L->ci->func);
api_check(L, cl->c.cont);
lua_call(L, nargs, nresults);
if (L->status == LUA_YIELD || L->status == LUA_BREAK)
return -1; // -1 is a marker for yielding from C
return cl->c.cont(L, LUA_OK);
}
/*
** {======================================================
** Generic Buffer manipulation

View file

@ -11,6 +11,8 @@
#include <stdio.h>
#include <stdlib.h>
LUAU_FASTFLAG(LuauYieldableContinuations)
static void writestring(const char* s, size_t l)
{
fwrite(s, 1, l, stdout);
@ -294,10 +296,18 @@ static int luaB_pcally(lua_State* L)
// any errors from this point on are handled by continuation
L->ci->flags |= LUA_CALLINFO_HANDLE;
// maintain yieldable invariant (baseCcalls <= nCcalls)
L->baseCcalls++;
if (!FFlag::LuauYieldableContinuations)
{
// maintain yieldable invariant (baseCcalls <= nCcalls)
L->baseCcalls++;
}
int status = luaD_pcall(L, luaB_pcallrun, func, savestack(L, func), 0);
L->baseCcalls--;
if (!FFlag::LuauYieldableContinuations)
{
L->baseCcalls--;
}
// necessary to accomodate functions that return lots of values
expandstacklimit(L, L->top);
@ -348,12 +358,20 @@ static int luaB_xpcally(lua_State* L)
StkId errf = L->base;
StkId func = L->base + 1;
// maintain yieldable invariant (baseCcalls <= nCcalls)
L->baseCcalls++;
int status = luaD_pcall(L, luaB_pcallrun, func, savestack(L, func), savestack(L, errf));
L->baseCcalls--;
if (!FFlag::LuauYieldableContinuations)
{
// maintain yieldable invariant (baseCcalls <= nCcalls)
L->baseCcalls++;
}
// necessary to accomodate functions that return lots of values
int status = luaD_pcall(L, luaB_pcallrun, func, savestack(L, func), savestack(L, errf));
if (!FFlag::LuauYieldableContinuations)
{
L->baseCcalls--;
}
// necessary to accommodate functions that return lots of values
expandstacklimit(L, L->top);
// yielding means we need to propagate yield; resume will call continuation function later

View file

@ -17,6 +17,8 @@
#include <string.h>
LUAU_FASTFLAGVARIABLE(LuauYieldableContinuations)
// keep max stack allocation request under 1GB
#define MAX_STACK_SIZE (int(1024 / sizeof(TValue)) * 1024 * 1024)
@ -260,24 +262,79 @@ void luaD_call(lua_State* L, StkId func, int nresults)
if (++L->nCcalls >= LUAI_MAXCCALLS)
luaD_checkCstack(L);
ptrdiff_t old_func = savestack(L, func);
if (FFlag::LuauYieldableContinuations)
{
// when called from a yieldable C function, maintain yieldable invariant (baseCcalls <= nCcalls)
bool fromyieldableccall = false;
if (luau_precall(L, func, nresults) == PCRLUA)
{ // is a Lua function?
L->ci->flags |= LUA_CALLINFO_RETURN; // luau_execute will stop after returning from the stack frame
if (L->ci != L->base_ci)
{
Closure* ccl = clvalue(L->ci->func);
bool oldactive = L->isactive;
L->isactive = true;
luaC_threadbarrier(L);
if (ccl->isC && ccl->c.cont)
{
fromyieldableccall = true;
L->baseCcalls++;
}
}
luau_execute(L); // call it
ptrdiff_t funcoffset = savestack(L, func);
ptrdiff_t cioffset = saveci(L, L->ci);
if (!oldactive)
L->isactive = false;
if (luau_precall(L, func, nresults) == PCRLUA)
{ // is a Lua function?
L->ci->flags |= LUA_CALLINFO_RETURN; // luau_execute will stop after returning from the stack frame
bool oldactive = L->isactive;
L->isactive = true;
luaC_threadbarrier(L);
luau_execute(L); // call it
if (!oldactive)
L->isactive = false;
}
bool yielded = L->status == LUA_YIELD || L->status == LUA_BREAK;
if (fromyieldableccall)
{
// restore original yieldable invariant
// in case of an error, this would either be restored by luaD_pcall or the thread would no longer be resumable
L->baseCcalls--;
// on yield, we have to set the CallInfo top of the C function including slots for expected results, to restore later
if (yielded)
{
CallInfo* callerci = restoreci(L, cioffset);
callerci->top = restorestack(L, funcoffset) + (nresults != LUA_MULTRET ? nresults : 0);
}
}
if (nresults != LUA_MULTRET && !yielded)
L->top = restorestack(L, funcoffset) + nresults;
}
else
{
ptrdiff_t old_func = savestack(L, func);
if (nresults != LUA_MULTRET)
L->top = restorestack(L, old_func) + nresults;
if (luau_precall(L, func, nresults) == PCRLUA)
{ // is a Lua function?
L->ci->flags |= LUA_CALLINFO_RETURN; // luau_execute will stop after returning from the stack frame
bool oldactive = L->isactive;
L->isactive = true;
luaC_threadbarrier(L);
luau_execute(L); // call it
if (!oldactive)
L->isactive = false;
}
if (nresults != LUA_MULTRET)
L->top = restorestack(L, old_func) + nresults;
}
L->nCcalls--;
luaC_checkGC(L);
@ -323,9 +380,18 @@ static void resume_continue(lua_State* L)
// C continuation; we expect this to be followed by Lua continuations
int n = cl->c.cont(L, 0);
// Continuation can break again
if (L->status == LUA_BREAK)
break;
if (FFlag::LuauYieldableContinuations)
{
// continuation can break or yield again
if (L->status == LUA_BREAK || L->status == LUA_YIELD)
break;
}
else
{
// Continuation can break again
if (L->status == LUA_BREAK)
break;
}
luau_poscall(L, L->top - n);
}
@ -370,6 +436,11 @@ static void resume(lua_State* L, void* ud)
// finish interrupted execution of `OP_CALL'
luau_poscall(L, firstArg);
}
else if (FFlag::LuauYieldableContinuations)
{
// restore arguments we have protected for C continuation
L->base = L->ci->base;
}
}
else
{
@ -576,6 +647,7 @@ static void restore_stack_limit(lua_State* L)
int luaD_pcall(lua_State* L, Pfunc func, void* u, ptrdiff_t old_top, ptrdiff_t ef)
{
unsigned short oldnCcalls = L->nCcalls;
unsigned short oldbaseCcalls = FFlag::LuauYieldableContinuations ? L->baseCcalls : 0;
ptrdiff_t old_ci = saveci(L, L->ci);
bool oldactive = L->isactive;
int status = luaD_rawrunprotected(L, func, u);
@ -612,6 +684,9 @@ int luaD_pcall(lua_State* L, Pfunc func, void* u, ptrdiff_t old_top, ptrdiff_t e
// restore nCcalls before calling the debugprotectederror callback which may rely on the proper value to have been restored.
L->nCcalls = oldnCcalls;
if (FFlag::LuauYieldableContinuations)
L->baseCcalls = oldbaseCcalls;
// an error occurred, check if we have a protected error callback
if (yieldable && L->global->cb.debugprotectederror)
{

View file

@ -124,8 +124,8 @@ int registerTypes(Luau::Frontend& frontend, Luau::GlobalTypes& globals, bool for
// Vector3 stub
TypeId vector3MetaType = arena.addType(TableType{});
TypeId vector3InstanceType = arena.addType(ClassType{"Vector3", {}, nullopt, vector3MetaType, {}, {}, "Test", {}});
getMutable<ClassType>(vector3InstanceType)->props = {
TypeId vector3InstanceType = arena.addType(ExternType{"Vector3", {}, nullopt, vector3MetaType, {}, {}, "Test", {}});
getMutable<ExternType>(vector3InstanceType)->props = {
{"X", {builtinTypes.numberType}},
{"Y", {builtinTypes.numberType}},
{"Z", {builtinTypes.numberType}},
@ -139,16 +139,16 @@ int registerTypes(Luau::Frontend& frontend, Luau::GlobalTypes& globals, bool for
globals.globalScope->exportedTypeBindings["Vector3"] = TypeFun{{}, vector3InstanceType};
// Instance stub
TypeId instanceType = arena.addType(ClassType{"Instance", {}, nullopt, nullopt, {}, {}, "Test", {}});
getMutable<ClassType>(instanceType)->props = {
TypeId instanceType = arena.addType(ExternType{"Instance", {}, nullopt, nullopt, {}, {}, "Test", {}});
getMutable<ExternType>(instanceType)->props = {
{"Name", {builtinTypes.stringType}},
};
globals.globalScope->exportedTypeBindings["Instance"] = TypeFun{{}, instanceType};
// Part stub
TypeId partType = arena.addType(ClassType{"Part", {}, instanceType, nullopt, {}, {}, "Test", {}});
getMutable<ClassType>(partType)->props = {
TypeId partType = arena.addType(ExternType{"Part", {}, instanceType, nullopt, {}, {}, "Test", {}});
getMutable<ExternType>(partType)->props = {
{"Position", {vector3InstanceType}},
};

View file

@ -36,7 +36,7 @@ static const std::string kTypes[] = {
"vector",
};
static const std::string kClasses[] = {
static const std::string kExternTypes[] = {
"Vector3",
"Instance",
"Part",
@ -902,8 +902,8 @@ struct ProtoToLuau
void print(const luau::TypeClass& type)
{
size_t index = size_t(type.kind()) % std::size(kClasses);
source += kClasses[index];
size_t index = size_t(type.kind()) % std::size(kExternTypes);
source += kExternTypes[index];
}
void print(const luau::TypeRef& type)

View file

@ -13,6 +13,7 @@ using namespace Luau;
LUAU_FASTFLAG(LuauAstTypeGroup3)
LUAU_FASTFLAG(LuauFixFunctionWithAttributesStartLocation)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
struct JsonEncoderFixture
{
@ -420,20 +421,39 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatDeclareFunction")
{
AstStat* statement = expectParseStatement("declare function foo(x: number): string");
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":[]})";
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
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":[]})";
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);
CHECK(toJson(statement) == expected);
}
}
TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatDeclareFunction2")
{
AstStat* statement = expectParseStatement("declare function foo(x: number, ...: string): string");
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":[]})";
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
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":[]})";
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")
@ -463,9 +483,18 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatDeclareClass")
REQUIRE(2 == root->body.size);
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);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
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})";
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 =
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})";
@ -476,7 +505,19 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_annotation")
{
AstStat* statement = expectParseStatement("type T = ((number) -> (string | nil)) & ((string) -> ())");
if (FFlag::LuauAstTypeGroup3)
if (FFlag::LuauStoreReturnTypesAsPackOnAst && FFlag::LuauAstTypeGroup3)
{
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})";
CHECK(toJson(statement) == expected);
}
else if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
std::string_view expected =
R"({"type":"AstStatTypeAlias","location":"0,0 - 0,55","name":"T","generics":[],"genericPacks":[],"value":{"type":"AstTypeIntersection","location":"0,9 - 0,55","types":[{"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":"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":"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);
}
else if (FFlag::LuauAstTypeGroup3)
{
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})";
@ -516,10 +557,18 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstTypeFunction")
{
AstStat* statement = expectParseStatement(R"(type fun = (string, bool, named: number) -> ())");
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);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
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})";
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")
@ -550,4 +599,63 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstTypePackExplicit")
CHECK(toJson(root->body.data[1]) == expected);
}
TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstGenericType")
{
AstStatBlock* root = expectParse(R"(
a = function<b, c>()
end
)");
CHECK(1 == root->body.size);
std::string_view expected =
R"({"type":"AstStatAssign","location":"1,8 - 2,11","vars":[{"type":"AstExprGlobal","location":"1,8 - 1,9","global":"a"}],"values":[{"type":"AstExprFunction","location":"1,12 - 2,11","attributes":[],"generics":[{"type":"AstGenericType","name":"b"},{"type":"AstGenericType","name":"c"}],"genericPacks":[],"args":[],"vararg":false,"varargLocation":"0,0 - 0,0","body":{"type":"AstStatBlock","location":"1,28 - 2,8","hasEnd":true,"body":[]},"functionDepth":1,"debugname":""}]})";
CHECK(toJson(root->body.data[0]) == expected);
}
TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstGenericTypeWithDefault")
{
AstStatBlock* root = expectParse(R"(
type Foo<X = string> = X
)");
CHECK(1 == root->body.size);
std::string_view expected =
R"({"type":"AstStatTypeAlias","location":"1,8 - 1,32","name":"Foo","generics":[{"type":"AstGenericType","name":"X","luauType":{"type":"AstTypeReference","location":"1,21 - 1,27","name":"string","nameLocation":"1,21 - 1,27","parameters":[]}}],"genericPacks":[],"value":{"type":"AstTypeReference","location":"1,31 - 1,32","name":"X","nameLocation":"1,31 - 1,32","parameters":[]},"exported":false})";
CHECK(toJson(root->body.data[0]) == expected);
}
TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstGenericTypePack")
{
AstStatBlock* root = expectParse(R"(
a = function<b..., c...>()
end
)");
CHECK(1 == root->body.size);
std::string_view expected =
R"({"type":"AstStatAssign","location":"1,8 - 2,11","vars":[{"type":"AstExprGlobal","location":"1,8 - 1,9","global":"a"}],"values":[{"type":"AstExprFunction","location":"1,12 - 2,11","attributes":[],"generics":[],"genericPacks":[{"type":"AstGenericTypePack","name":"b"},{"type":"AstGenericTypePack","name":"c"}],"args":[],"vararg":false,"varargLocation":"0,0 - 0,0","body":{"type":"AstStatBlock","location":"1,34 - 2,8","hasEnd":true,"body":[]},"functionDepth":1,"debugname":""}]})";
CHECK(toJson(root->body.data[0]) == expected);
}
TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstGenericTypePackWithDefault")
{
AstStatBlock* root = expectParse(R"(
type Foo<X... = ...string> = any
)");
CHECK(1 == root->body.size);
std::string_view expected =
R"({"type":"AstStatTypeAlias","location":"1,8 - 1,40","name":"Foo","generics":[],"genericPacks":[{"type":"AstGenericTypePack","name":"X","luauType":{"type":"AstTypePackVariadic","location":"1,24 - 1,33","variadicType":{"type":"AstTypeReference","location":"1,27 - 1,33","name":"string","nameLocation":"1,27 - 1,33","parameters":[]}}}],"value":{"type":"AstTypeReference","location":"1,37 - 1,40","name":"any","nameLocation":"1,37 - 1,40","parameters":[]},"exported":false})";
CHECK(toJson(root->body.data[0]) == expected);
}
TEST_SUITE_END();

View file

@ -20,14 +20,13 @@ LUAU_FASTFLAG(LuauTraceTypesInNonstrictMode2)
LUAU_FASTFLAG(LuauSetMetatableDoesNotTimeTravel)
LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAG(LuauExposeRequireByStringAutocomplete)
LUAU_FASTFLAG(LuauAutocompleteUnionCopyPreviousSeen)
LUAU_FASTFLAG(LuauUserTypeFunTypecheck)
LUAU_FASTFLAG(LuauTypeFunResultInAutocomplete)
using namespace Luau;
static std::optional<AutocompleteEntryMap> nullCallback(std::string tag, std::optional<const ClassType*> ptr, std::optional<std::string> contents)
static std::optional<AutocompleteEntryMap> nullCallback(std::string tag, std::optional<const ExternType*> ptr, std::optional<std::string> contents)
{
return std::nullopt;
}
@ -159,7 +158,7 @@ struct ACBuiltinsFixture : ACFixtureImpl<BuiltinsFixture>
{
};
struct ACClassFixture : ACFixtureImpl<ClassFixture>
struct ACExternTypeFixture : ACFixtureImpl<ExternTypeFixture>
{
};
@ -3754,7 +3753,7 @@ TEST_CASE_FIXTURE(ACFixture, "string_contents_is_available_to_callback")
bool isCorrect = false;
auto ac1 = autocomplete(
'1',
[&isCorrect](std::string, std::optional<const ClassType*>, std::optional<std::string> contents) -> std::optional<AutocompleteEntryMap>
[&isCorrect](std::string, std::optional<const ExternType*>, std::optional<std::string> contents) -> std::optional<AutocompleteEntryMap>
{
isCorrect = contents && *contents == "testing/";
return std::nullopt;
@ -3766,8 +3765,6 @@ TEST_CASE_FIXTURE(ACFixture, "string_contents_is_available_to_callback")
TEST_CASE_FIXTURE(ACBuiltinsFixture, "require_by_string")
{
ScopedFastFlag sff{FFlag::LuauExposeRequireByStringAutocomplete, true};
fileResolver.source["MainModule"] = R"(
local info = "MainModule serves as the root directory"
)";
@ -3960,7 +3957,7 @@ TEST_CASE_FIXTURE(ACFixture, "string_completion_outside_quotes")
local x = require(@1"@2"@3)
)");
StringCompletionCallback callback = [](std::string, std::optional<const ClassType*>, std::optional<std::string> contents
StringCompletionCallback callback = [](std::string, std::optional<const ExternType*>, std::optional<std::string> contents
) -> std::optional<AutocompleteEntryMap>
{
Luau::AutocompleteEntryMap results = {{"test", Luau::AutocompleteEntry{Luau::AutocompleteEntryKind::String, std::nullopt, false, false}}};
@ -4432,7 +4429,7 @@ local x = 1 + result.
CHECK(ac.entryMap.count("x"));
}
TEST_CASE_FIXTURE(ACClassFixture, "ac_dont_overflow_on_recursive_union")
TEST_CASE_FIXTURE(ACExternTypeFixture, "ac_dont_overflow_on_recursive_union")
{
ScopedFastFlag _{FFlag::LuauAutocompleteUnionCopyPreviousSeen, true};
check(R"(

View file

@ -28,9 +28,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "lib_documentation_symbols")
{
props = &ttv->props;
}
else if (const ClassType* ctv = get<ClassType>(binding.typeId))
else if (const ExternType* etv = get<ExternType>(binding.typeId))
{
props = &ctv->props;
props = &etv->props;
}
if (props)

View file

@ -9,7 +9,7 @@ using std::nullopt;
namespace Luau
{
ClassFixture::ClassFixture(bool prepareAutocomplete)
ExternTypeFixture::ExternTypeFixture(bool prepareAutocomplete)
: BuiltinsFixture(prepareAutocomplete)
{
GlobalTypes& globals = frontend.globals;
@ -19,22 +19,22 @@ ClassFixture::ClassFixture(bool prepareAutocomplete)
unfreeze(arena);
TypeId connectionType = arena.addType(ClassType{"Connection", {}, nullopt, nullopt, {}, {}, "Connection", {}});
TypeId connectionType = arena.addType(ExternType{"Connection", {}, nullopt, nullopt, {}, {}, "Connection", {}});
TypeId baseClassInstanceType = arena.addType(ClassType{"BaseClass", {}, nullopt, nullopt, {}, {}, "Test", {}});
getMutable<ClassType>(baseClassInstanceType)->props = {
TypeId baseClassInstanceType = arena.addType(ExternType{"BaseClass", {}, nullopt, nullopt, {}, {}, "Test", {}});
getMutable<ExternType>(baseClassInstanceType)->props = {
{"BaseMethod", Property::readonly(makeFunction(arena, baseClassInstanceType, {numberType}, {}))},
{"BaseField", {numberType}},
{"Touched", Property::readonly(connectionType)},
};
getMutable<ClassType>(connectionType)->props = {
getMutable<ExternType>(connectionType)->props = {
{"Connect", {makeFunction(arena, connectionType, {makeFunction(arena, nullopt, {baseClassInstanceType}, {})}, {})}}
};
TypeId baseClassType = arena.addType(ClassType{"BaseClass", {}, nullopt, nullopt, {}, {}, "Test", {}});
getMutable<ClassType>(baseClassType)->props = {
TypeId baseClassType = arena.addType(ExternType{"BaseClass", {}, nullopt, nullopt, {}, {}, "Test", {}});
getMutable<ExternType>(baseClassType)->props = {
{"StaticMethod", {makeFunction(arena, nullopt, {}, {numberType})}},
{"Clone", {makeFunction(arena, nullopt, {baseClassInstanceType}, {baseClassInstanceType})}},
{"New", {makeFunction(arena, nullopt, {}, {baseClassInstanceType})}},
@ -42,49 +42,49 @@ ClassFixture::ClassFixture(bool prepareAutocomplete)
globals.globalScope->exportedTypeBindings["BaseClass"] = TypeFun{{}, baseClassInstanceType};
addGlobalBinding(globals, "BaseClass", baseClassType, "@test");
TypeId childClassInstanceType = arena.addType(ClassType{"ChildClass", {}, baseClassInstanceType, nullopt, {}, {}, "Test", {}});
TypeId childClassInstanceType = arena.addType(ExternType{"ChildClass", {}, baseClassInstanceType, nullopt, {}, {}, "Test", {}});
getMutable<ClassType>(childClassInstanceType)->props = {
getMutable<ExternType>(childClassInstanceType)->props = {
{"Method", {makeFunction(arena, childClassInstanceType, {}, {stringType})}},
};
TypeId childClassType = arena.addType(ClassType{"ChildClass", {}, baseClassType, nullopt, {}, {}, "Test", {}});
getMutable<ClassType>(childClassType)->props = {
TypeId childClassType = arena.addType(ExternType{"ChildClass", {}, baseClassType, nullopt, {}, {}, "Test", {}});
getMutable<ExternType>(childClassType)->props = {
{"New", {makeFunction(arena, nullopt, {}, {childClassInstanceType})}},
};
globals.globalScope->exportedTypeBindings["ChildClass"] = TypeFun{{}, childClassInstanceType};
addGlobalBinding(globals, "ChildClass", childClassType, "@test");
TypeId grandChildInstanceType = arena.addType(ClassType{"GrandChild", {}, childClassInstanceType, nullopt, {}, {}, "Test", {}});
TypeId grandChildInstanceType = arena.addType(ExternType{"GrandChild", {}, childClassInstanceType, nullopt, {}, {}, "Test", {}});
getMutable<ClassType>(grandChildInstanceType)->props = {
getMutable<ExternType>(grandChildInstanceType)->props = {
{"Method", {makeFunction(arena, grandChildInstanceType, {}, {stringType})}},
};
TypeId grandChildType = arena.addType(ClassType{"GrandChild", {}, baseClassType, nullopt, {}, {}, "Test", {}});
getMutable<ClassType>(grandChildType)->props = {
TypeId grandChildType = arena.addType(ExternType{"GrandChild", {}, baseClassType, nullopt, {}, {}, "Test", {}});
getMutable<ExternType>(grandChildType)->props = {
{"New", {makeFunction(arena, nullopt, {}, {grandChildInstanceType})}},
};
globals.globalScope->exportedTypeBindings["GrandChild"] = TypeFun{{}, grandChildInstanceType};
addGlobalBinding(globals, "GrandChild", childClassType, "@test");
TypeId anotherChildInstanceType = arena.addType(ClassType{"AnotherChild", {}, baseClassInstanceType, nullopt, {}, {}, "Test", {}});
TypeId anotherChildInstanceType = arena.addType(ExternType{"AnotherChild", {}, baseClassInstanceType, nullopt, {}, {}, "Test", {}});
getMutable<ClassType>(anotherChildInstanceType)->props = {
getMutable<ExternType>(anotherChildInstanceType)->props = {
{"Method", {makeFunction(arena, anotherChildInstanceType, {}, {stringType})}},
};
TypeId anotherChildType = arena.addType(ClassType{"AnotherChild", {}, baseClassType, nullopt, {}, {}, "Test", {}});
getMutable<ClassType>(anotherChildType)->props = {
TypeId anotherChildType = arena.addType(ExternType{"AnotherChild", {}, baseClassType, nullopt, {}, {}, "Test", {}});
getMutable<ExternType>(anotherChildType)->props = {
{"New", {makeFunction(arena, nullopt, {}, {anotherChildInstanceType})}},
};
globals.globalScope->exportedTypeBindings["AnotherChild"] = TypeFun{{}, anotherChildInstanceType};
addGlobalBinding(globals, "AnotherChild", childClassType, "@test");
TypeId unrelatedClassInstanceType = arena.addType(ClassType{"UnrelatedClass", {}, nullopt, nullopt, {}, {}, "Test", {}});
TypeId unrelatedClassInstanceType = arena.addType(ExternType{"UnrelatedClass", {}, nullopt, nullopt, {}, {}, "Test", {}});
TypeId unrelatedClassType = arena.addType(ClassType{"UnrelatedClass", {}, nullopt, nullopt, {}, {}, "Test", {}});
getMutable<ClassType>(unrelatedClassType)->props = {
TypeId unrelatedClassType = arena.addType(ExternType{"UnrelatedClass", {}, nullopt, nullopt, {}, {}, "Test", {}});
getMutable<ExternType>(unrelatedClassType)->props = {
{"New", {makeFunction(arena, nullopt, {}, {unrelatedClassInstanceType})}},
};
globals.globalScope->exportedTypeBindings["UnrelatedClass"] = TypeFun{{}, unrelatedClassInstanceType};
@ -92,14 +92,14 @@ ClassFixture::ClassFixture(bool prepareAutocomplete)
TypeId vector2MetaType = arena.addType(TableType{});
vector2InstanceType = arena.addType(ClassType{"Vector2", {}, nullopt, vector2MetaType, {}, {}, "Test", {}});
getMutable<ClassType>(vector2InstanceType)->props = {
vector2InstanceType = arena.addType(ExternType{"Vector2", {}, nullopt, vector2MetaType, {}, {}, "Test", {}});
getMutable<ExternType>(vector2InstanceType)->props = {
{"X", {numberType}},
{"Y", {numberType}},
};
vector2Type = arena.addType(ClassType{"Vector2", {}, nullopt, nullopt, {}, {}, "Test", {}});
getMutable<ClassType>(vector2Type)->props = {
vector2Type = arena.addType(ExternType{"Vector2", {}, nullopt, nullopt, {}, {}, "Test", {}});
getMutable<ExternType>(vector2Type)->props = {
{"New", {makeFunction(arena, nullopt, {numberType, numberType}, {vector2InstanceType})}},
};
getMutable<TableType>(vector2MetaType)->props = {
@ -114,7 +114,7 @@ ClassFixture::ClassFixture(bool prepareAutocomplete)
addGlobalBinding(globals, "Vector2", vector2Type, "@test");
TypeId callableClassMetaType = arena.addType(TableType{});
TypeId callableClassType = arena.addType(ClassType{"CallableClass", {}, nullopt, callableClassMetaType, {}, {}, "Test", {}});
TypeId callableClassType = arena.addType(ExternType{"CallableClass", {}, nullopt, callableClassMetaType, {}, {}, "Test", {}});
getMutable<TableType>(callableClassMetaType)->props = {
{"__call", {makeFunction(arena, nullopt, {callableClassType, stringType}, {numberType})}},
};
@ -124,7 +124,7 @@ ClassFixture::ClassFixture(bool prepareAutocomplete)
{
TypeId indexableClassMetaType = arena.addType(TableType{});
TypeId indexableClassType =
arena.addType(ClassType{className, {}, nullopt, indexableClassMetaType, {}, {}, "Test", {}, TableIndexer{keyType, returnType}});
arena.addType(ExternType{className, {}, nullopt, indexableClassMetaType, {}, {}, "Test", {}, TableIndexer{keyType, returnType}});
globals.globalScope->exportedTypeBindings[className] = TypeFun{{}, indexableClassType};
};
@ -134,9 +134,9 @@ ClassFixture::ClassFixture(bool prepareAutocomplete)
addIndexableClass("IndexableNumericKeyClass", numberType, numberType);
// Add a confusing derived class which shares the same name internally, but has a unique alias
TypeId duplicateBaseClassInstanceType = arena.addType(ClassType{"BaseClass", {}, baseClassInstanceType, nullopt, {}, {}, "Test", {}});
TypeId duplicateBaseClassInstanceType = arena.addType(ExternType{"BaseClass", {}, baseClassInstanceType, nullopt, {}, {}, "Test", {}});
getMutable<ClassType>(duplicateBaseClassInstanceType)->props = {
getMutable<ExternType>(duplicateBaseClassInstanceType)->props = {
{"Method", {makeFunction(arena, duplicateBaseClassInstanceType, {}, {stringType})}},
};

View file

@ -6,9 +6,9 @@
namespace Luau
{
struct ClassFixture : BuiltinsFixture
struct ExternTypeFixture : BuiltinsFixture
{
explicit ClassFixture(bool prepareAutocomplete = false);
explicit ExternTypeFixture(bool prepareAutocomplete = false);
TypeId vector2Type;
TypeId vector2InstanceType;

View file

@ -35,6 +35,7 @@ LUAU_FASTFLAG(LuauLibWhereErrorAutoreserve)
LUAU_FASTFLAG(DebugLuauAbortingChecks)
LUAU_FASTINT(CodegenHeuristicsInstructionLimit)
LUAU_DYNAMIC_FASTFLAG(LuauStringFormatFixC)
LUAU_FASTFLAG(LuauYieldableContinuations)
static lua_CompileOptions defaultOptions()
{
@ -823,6 +824,236 @@ TEST_CASE("Pack")
runConformance("tpack.luau");
}
int singleYield(lua_State* L)
{
lua_pushnumber(L, 2);
return lua_yield(L, 1);
}
int singleYieldContinuation(lua_State* L, int status)
{
lua_pushnumber(L, 4);
return 1;
}
int multipleYields(lua_State* L)
{
lua_settop(L, 1); // Only 1 argument expected
int base = luaL_checkinteger(L, 1);
luaL_checkstack(L, 2, "cmultiyield");
// current state
int pos = 1;
lua_pushinteger(L, pos);
// return value
lua_pushinteger(L, base + pos);
return lua_yield(L, 1);
}
int multipleYieldsContinuation(lua_State* L, int status)
{
// function arguments are still alive
int base = luaL_checkinteger(L, 1);
// function state is still alive
int pos = luaL_checkinteger(L, 2) + 1;
luaL_checkstack(L, 1, "cmultiyieldcont");
lua_pushinteger(L, pos);
lua_replace(L, 2);
luaL_checkstack(L, 1, "cmultiyieldcont");
if (pos < 4)
{
lua_pushinteger(L, base + pos);
return lua_yield(L, 1);
}
else
{
lua_pushinteger(L, base + pos);
return 1;
}
}
int nestedMultipleYieldHelper(lua_State* L)
{
int context = luaL_checkinteger(L, lua_upvalueindex(1));
lua_pushinteger(L, 100 + context);
return lua_yield(L, 1);
}
int nestedMultipleYieldHelperContinuation(lua_State* L, int status)
{
int context = luaL_checkinteger(L, lua_upvalueindex(1));
lua_pushinteger(L, 110 + context);
return 1;
}
int nestedMultipleYieldHelperNonYielding(lua_State* L)
{
int context = luaL_checkinteger(L, lua_upvalueindex(1));
lua_pushinteger(L, 105 + context);
return 1;
}
int multipleYieldsWithNestedCall(lua_State* L)
{
lua_settop(L, 2); // Only 2 arguments expected
bool nestedShouldYield = luaL_checkboolean(L, 2);
lua_pushinteger(L, 0); // state
lua_pushnumber(L, 5);
if (nestedShouldYield)
lua_pushcclosurek(L, nestedMultipleYieldHelper, nullptr, 1, nestedMultipleYieldHelperContinuation);
else
lua_pushcclosurek(L, nestedMultipleYieldHelperNonYielding, nullptr, 1, nullptr);
return luaL_callyieldable(L, 0, 1);
}
int multipleYieldsWithNestedCallContinuation(lua_State* L, int status)
{
int state = luaL_checkinteger(L, 3);
luaL_checkstack(L, 1, "cnestedmultiyieldcont");
lua_pushinteger(L, state + 1);
lua_replace(L, 3);
if (state == 0)
{
return lua_yield(L, lua_gettop(L) - 3);
}
else if (state == 1)
{
lua_pushnumber(L, luaL_checkinteger(L, 1) + 200);
return lua_yield(L, 1);
}
else
{
lua_pushnumber(L, luaL_checkinteger(L, 1) + 210);
return 1;
}
}
int passthroughCall(lua_State* L)
{
luaL_checkstack(L, 3, "cpass");
lua_pushvalue(L, 1);
lua_pushvalue(L, 2);
lua_pushvalue(L, 3);
return luaL_callyieldable(L, 2, 1);
}
int passthroughCallContinuation(lua_State* L, int status)
{
LUAU_ASSERT(lua_gettop(L) == 4); // 3 original arguments and the return value
LUAU_ASSERT(lua_tonumber(L, -1) == 0.5);
return 1;
}
int passthroughCallMoreResults(lua_State* L)
{
luaL_checkstack(L, 3, "cpass");
lua_pushvalue(L, 1);
lua_pushvalue(L, 2);
lua_pushvalue(L, 3);
return luaL_callyieldable(L, 2, 10);
}
int passthroughCallMoreResultsContinuation(lua_State* L, int status)
{
LUAU_ASSERT(lua_gettop(L) == 13); // 3 original arguments and 10 requested return values
for (int i = 0; i < 9; i++)
{
LUAU_ASSERT(lua_isnil(L, -1));
lua_pop(L, 1);
}
LUAU_ASSERT(lua_tonumber(L, -1) == 0.5);
return 1;
}
int passthroughCallArgReuse(lua_State* L)
{
return luaL_callyieldable(L, 2, 1);
}
int passthroughCallArgReuseContinuation(lua_State* L, int status)
{
LUAU_ASSERT(lua_gettop(L) == 1); // Original arguments were consumed, only return remains
LUAU_ASSERT(lua_tonumber(L, -1) == 0.5);
return 1;
}
int passthroughCallVaradic(lua_State* L)
{
luaL_checkany(L, 1);
return luaL_callyieldable(L, lua_gettop(L) - 1, LUA_MULTRET);
}
int passthroughCallVaradicContinuation(lua_State* L, int status)
{
return lua_gettop(L);
}
int passthroughCallWithState(lua_State* L)
{
luaL_checkany(L, 1);
int args = lua_gettop(L) - 1;
lua_pushnumber(L, 42);
lua_insert(L, 1);
return luaL_callyieldable(L, args, LUA_MULTRET);
}
int passthroughCallWithStateContinuation(lua_State* L, int status)
{
LUAU_ASSERT(luaL_checkinteger(L, 1) == 42);
return lua_gettop(L) - 1;
}
TEST_CASE("CYield")
{
ScopedFastFlag luauYieldableContinuations{FFlag::LuauYieldableContinuations, true};
runConformance(
"cyield.luau",
[](lua_State* L)
{
lua_pushcclosurek(L, singleYield, "singleYield", 0, singleYieldContinuation);
lua_setglobal(L, "singleYield");
lua_pushcclosurek(L, multipleYields, "multipleYields", 0, multipleYieldsContinuation);
lua_setglobal(L, "multipleYields");
lua_pushcclosurek(L, multipleYieldsWithNestedCall, "multipleYieldsWithNestedCall", 0, multipleYieldsWithNestedCallContinuation);
lua_setglobal(L, "multipleYieldsWithNestedCall");
lua_pushcclosurek(L, passthroughCall, "passthroughCall", 0, passthroughCallContinuation);
lua_setglobal(L, "passthroughCall");
lua_pushcclosurek(L, passthroughCallMoreResults, "passthroughCallMoreResults", 0, passthroughCallMoreResultsContinuation);
lua_setglobal(L, "passthroughCallMoreResults");
lua_pushcclosurek(L, passthroughCallArgReuse, "passthroughCallArgReuse", 0, passthroughCallArgReuseContinuation);
lua_setglobal(L, "passthroughCallArgReuse");
lua_pushcclosurek(L, passthroughCallVaradic, "passthroughCallVaradic", 0, passthroughCallVaradicContinuation);
lua_setglobal(L, "passthroughCallVaradic");
lua_pushcclosurek(L, passthroughCallWithState, "passthroughCallWithState", 0, passthroughCallWithStateContinuation);
lua_setglobal(L, "passthroughCallWithState");
}
);
}
TEST_CASE("Vector")
{
lua_CompileOptions copts = defaultOptions();
@ -957,7 +1188,7 @@ static void populateRTTI(lua_State* L, Luau::TypeId type)
lua_pushstring(L, "function");
}
else if (auto c = Luau::get<Luau::ClassType>(type))
else if (auto c = Luau::get<Luau::ExternType>(type))
{
lua_pushstring(L, c->name.c_str());
}

View file

@ -13,6 +13,7 @@
using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTFLAG(LuauDoNotAddUpvalueTypesToLocalType)
struct DataFlowGraphFixture
{
@ -421,6 +422,8 @@ TEST_CASE_FIXTURE(DataFlowGraphFixture, "property_lookup_on_a_phi_node_3")
TEST_CASE_FIXTURE(DataFlowGraphFixture, "function_captures_are_phi_nodes_of_all_versions")
{
ScopedFastFlag _{FFlag::LuauDoNotAddUpvalueTypesToLocalType, true};
dfg(R"(
local x = 5
@ -439,15 +442,14 @@ TEST_CASE_FIXTURE(DataFlowGraphFixture, "function_captures_are_phi_nodes_of_all_
DefId x4 = getDef<AstExprLocal, 3>(); // x = "five"
CHECK(x1 != x2);
CHECK(x2 != x3);
CHECK(x2 == x3);
CHECK(x3 != x4);
const Phi* phi = get<Phi>(x2);
REQUIRE(phi);
REQUIRE(phi->operands.size() == 3);
REQUIRE(phi->operands.size() == 2);
CHECK(phi->operands.at(0) == x1);
CHECK(phi->operands.at(1) == x3);
CHECK(phi->operands.at(2) == x4);
CHECK(phi->operands.at(1) == x4);
}
TEST_CASE_FIXTURE(DataFlowGraphFixture, "function_captures_are_phi_nodes_of_all_versions_properties")

Some files were not shown because too many files have changed in this diff Show more