Compare commits

..

7 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
Vighnesh-V
a2303a6ae6
Sync to upstream/release/670 (#1779)
# General 
This week has been focused primarily on bugfixes, with a ton of
usability improvements to the new solver, fragment autocomplete, and the
concrete syntax tree project.

## Runtime
- Fix an assertion caused by failing to allocate native code pages.
- Expose a `lua_pushrequire` function, which performs the same
initialization steps as `luaopen_require` but does not register require
globally. This lets users create specialized, custom `requires`.

# New Solver
- Fix a bug in simplification of types caused by combinatorial explosion
of intersection and union types.
- Fix a memory leak in fragment autocomplete
- Improve the isolation of modules in fragment autocomplete
- Throw errors when users define a type function with the name `typeof`
- Continue to narrow intersection types which might be `never`.
- Major rework of generalization continues - we are blazing a new path
with eager + non-reentrant generalization and actively working to make
these more performant and less error prone.
- Improve the ability of `and/or` type functions to reduce, even when
their arguments are generic.
- Report arity mismatches for undersaturated calls with unknown
parameters

# New Non-Strict
- Extends the new non-strict mode to report unknown symbols in types 

# Old Solver
- Fix a crash caused by excessive stack usage during typechecking

# Misc
- Improvements to Concrete Syntax Tree location tracking for string
table props.

---
Co-authored-by: Andy Friesen <afriesen@roblox.com>
Co-authored-by: Ariel Weiss <aaronweiss@roblox.com>
Co-authored-by: Aviral Goel <agoel@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-18 13:44:39 -07:00
Hunter Goldstein
d110c812bb
Disable LuauNonReentrantGeneralization for some tests (#1775)
For now, this flag causes a stack overflow for some tests on
Windows: we end up minting a massive recursive intersection during
generalization. Let's flip it off until a fix arrives.
2025-04-14 16:55:31 -07:00
Maidenless
b6457801c7
Update Arch Linux installation instructions (#1774) 2025-04-14 08:47:49 -07:00
Varun Saini
50f32a1400
Do not store file extensions in module chunknames [Luau CLI] (#1772) 2025-04-13 10:31:45 -07:00
177 changed files with 7448 additions and 3051 deletions

View file

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

View file

@ -3,6 +3,7 @@
#include "Luau/Ast.h" #include "Luau/Ast.h"
#include "Luau/Constraint.h" #include "Luau/Constraint.h"
#include "Luau/ConstraintSet.h"
#include "Luau/ControlFlow.h" #include "Luau/ControlFlow.h"
#include "Luau/DataFlowGraph.h" #include "Luau/DataFlowGraph.h"
#include "Luau/EqSatSimplification.h" #include "Luau/EqSatSimplification.h"
@ -91,9 +92,8 @@ struct ConstraintGenerator
// Constraints that go straight to the solver. // Constraints that go straight to the solver.
std::vector<ConstraintPtr> constraints; std::vector<ConstraintPtr> constraints;
// Constraints that do not go to the solver right away. Other constraints // The set of all free types introduced during constraint generation.
// will enqueue them during solving. DenseHashSet<TypeId> freeTypes{nullptr};
std::vector<ConstraintPtr> unqueuedConstraints;
// Map a function's signature scope back to its signature type. // Map a function's signature scope back to its signature type.
DenseHashMap<Scope*, TypeId> scopeToFunction{nullptr}; DenseHashMap<Scope*, TypeId> scopeToFunction{nullptr};
@ -151,6 +151,9 @@ struct ConstraintGenerator
std::vector<RequireCycle> requireCycles 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 * The entry point to the ConstraintGenerator. This will construct a set
* of scopes, constraints, and free types that can be solved later. * 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, AstStatTypeAlias* alias);
ControlFlow visit(const ScopePtr& scope, AstStatTypeFunction* function); ControlFlow visit(const ScopePtr& scope, AstStatTypeFunction* function);
ControlFlow visit(const ScopePtr& scope, AstStatDeclareGlobal* declareGlobal); 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, AstStatDeclareFunction* declareFunction);
ControlFlow visit(const ScopePtr& scope, AstStatError* error); ControlFlow visit(const ScopePtr& scope, AstStatError* error);
@ -481,9 +484,4 @@ private:
TypeId simplifyUnion(const ScopePtr& scope, Location location, TypeId left, TypeId right); 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 } // 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 #pragma once
#include "Luau/Constraint.h" #include "Luau/Constraint.h"
#include "Luau/ConstraintSet.h"
#include "Luau/DataFlowGraph.h" #include "Luau/DataFlowGraph.h"
#include "Luau/DenseHash.h" #include "Luau/DenseHash.h"
#include "Luau/EqSatSimplification.h" #include "Luau/EqSatSimplification.h"
@ -87,6 +88,7 @@ struct ConstraintSolver
NotNull<Simplifier> simplifier; NotNull<Simplifier> simplifier;
NotNull<TypeFunctionRuntime> typeFunctionRuntime; NotNull<TypeFunctionRuntime> typeFunctionRuntime;
// The entire set of constraints that the solver is trying to resolve. // The entire set of constraints that the solver is trying to resolve.
ConstraintSet constraintSet;
std::vector<NotNull<Constraint>> constraints; std::vector<NotNull<Constraint>> constraints;
NotNull<DenseHashMap<Scope*, TypeId>> scopeToFunction; NotNull<DenseHashMap<Scope*, TypeId>> scopeToFunction;
NotNull<Scope> rootScope; NotNull<Scope> rootScope;
@ -140,6 +142,19 @@ struct ConstraintSolver
DenseHashMap<TypeId, const Constraint*> typeFunctionsToFinalize{nullptr}; 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( explicit ConstraintSolver(
NotNull<Normalizer> normalizer, NotNull<Normalizer> normalizer,
NotNull<Simplifier> simplifier, NotNull<Simplifier> simplifier,
@ -174,6 +189,9 @@ struct ConstraintSolver
bool isDone() const; bool isDone() const;
private: private:
/// A helper that does most of the setup work that is shared between the two constructors.
void initFreeTypeTracking();
void generalizeOneType(TypeId ty); void generalizeOneType(TypeId ty);
/** /**
@ -432,6 +450,10 @@ public:
void fillInDiscriminantTypes(NotNull<const Constraint> constraint, const std::vector<std::optional<TypeId>>& discriminantTypes); 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); void dump(NotNull<Scope> rootScope, struct ToStringOptions& opts);
} // namespace Luau } // namespace Luau

View file

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

View file

@ -332,11 +332,11 @@ struct TypePackMismatch
bool operator==(const TypePackMismatch& rhs) const; bool operator==(const TypePackMismatch& rhs) const;
}; };
struct DynamicPropertyLookupOnClassesUnsafe struct DynamicPropertyLookupOnExternTypesUnsafe
{ {
TypeId ty; TypeId ty;
bool operator==(const DynamicPropertyLookupOnClassesUnsafe& rhs) const; bool operator==(const DynamicPropertyLookupOnExternTypesUnsafe& rhs) const;
}; };
struct UninhabitedTypeFunction struct UninhabitedTypeFunction
@ -455,6 +455,13 @@ struct UserDefinedTypeFunctionError
bool operator==(const UserDefinedTypeFunctionError& rhs) const; bool operator==(const UserDefinedTypeFunctionError& rhs) const;
}; };
struct ReservedIdentifier
{
std::string name;
bool operator==(const ReservedIdentifier& rhs) const;
};
using TypeErrorData = Variant< using TypeErrorData = Variant<
TypeMismatch, TypeMismatch,
UnknownSymbol, UnknownSymbol,
@ -492,7 +499,7 @@ using TypeErrorData = Variant<
TypesAreUnrelated, TypesAreUnrelated,
NormalizationTooComplex, NormalizationTooComplex,
TypePackMismatch, TypePackMismatch,
DynamicPropertyLookupOnClassesUnsafe, DynamicPropertyLookupOnExternTypesUnsafe,
UninhabitedTypeFunction, UninhabitedTypeFunction,
UninhabitedTypePackFunction, UninhabitedTypePackFunction,
WhereClauseNeeded, WhereClauseNeeded,
@ -504,7 +511,8 @@ using TypeErrorData = Variant<
UnexpectedTypeInSubtyping, UnexpectedTypeInSubtyping,
UnexpectedTypePackInSubtyping, UnexpectedTypePackInSubtyping,
ExplicitFunctionAnnotationRecommended, ExplicitFunctionAnnotationRecommended,
UserDefinedTypeFunctionError>; UserDefinedTypeFunctionError,
ReservedIdentifier>;
struct TypeErrorSummary struct TypeErrorSummary
{ {

View file

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

View file

@ -16,8 +16,24 @@ struct GeneralizationParams
Polarity polarity = Polarity::None; Polarity polarity = Polarity::None;
}; };
template<typename TID>
struct GeneralizationResult
{
std::optional<TID> result;
// True if the provided type was replaced with a generic.
bool wasReplacedByGeneric = false;
bool resourceLimitsExceeded = false;
explicit operator bool() const
{
return bool(result);
}
};
// Replace a single free type by its bounds according to the polarity provided. // Replace a single free type by its bounds according to the polarity provided.
std::optional<TypeId> generalizeType( GeneralizationResult<TypeId> generalizeType(
NotNull<TypeArena> arena, NotNull<TypeArena> arena,
NotNull<BuiltinTypes> builtinTypes, NotNull<BuiltinTypes> builtinTypes,
NotNull<Scope> scope, NotNull<Scope> scope,
@ -26,7 +42,7 @@ std::optional<TypeId> generalizeType(
); );
// Generalize one type pack // Generalize one type pack
std::optional<TypePackId> generalizeTypePack( GeneralizationResult<TypePackId> generalizeTypePack(
NotNull<TypeArena> arena, NotNull<TypeArena> arena,
NotNull<BuiltinTypes> builtinTypes, NotNull<BuiltinTypes> builtinTypes,
NotNull<Scope> scope, NotNull<Scope> scope,
@ -36,11 +52,31 @@ std::optional<TypePackId> generalizeTypePack(
void sealTable(NotNull<Scope> scope, TypeId ty); void sealTable(NotNull<Scope> scope, TypeId ty);
/** Attempt to generalize a type.
*
* If generalizationTarget is set, then only that type will be replaced by its
* bounds. The way this is intended to be used is that ty is some function that
* is not fully generalized, and generalizationTarget is a type within its
* signature. There should be no further constraints that could affect the
* bounds of generalizationTarget.
*
* Returns nullopt if generalization failed due to resources limits.
*/
std::optional<TypeId> generalize( std::optional<TypeId> generalize(
NotNull<TypeArena> arena,
NotNull<BuiltinTypes> builtinTypes,
NotNull<Scope> scope,
NotNull<DenseHashSet<TypeId>> cachedTypes,
TypeId ty,
std::optional<TypeId> generalizationTarget = {}
);
void pruneUnnecessaryGenerics(
NotNull<TypeArena> arena, NotNull<TypeArena> arena,
NotNull<BuiltinTypes> builtinTypes, NotNull<BuiltinTypes> builtinTypes,
NotNull<Scope> scope, NotNull<Scope> scope,
NotNull<DenseHashSet<TypeId>> cachedTypes, NotNull<DenseHashSet<TypeId>> cachedTypes,
TypeId ty TypeId ty
); );
}
} // namespace Luau

View file

@ -133,9 +133,9 @@ struct GenericTypeFinder : TypeOnceVisitor
return false; 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; return false;
} }
}; };

View file

@ -181,7 +181,7 @@ struct NormalizedStringType
bool isSubtype(const NormalizedStringType& subStr, const NormalizedStringType& superStr); bool isSubtype(const NormalizedStringType& subStr, const NormalizedStringType& superStr);
struct NormalizedClassType struct NormalizedExternType
{ {
/** Has the following structure: /** Has the following structure:
* *
@ -192,7 +192,7 @@ struct NormalizedClassType
* *
* Each TypeId is a class type. * 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 * 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 // 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 // * T is a union of table types
// * F is a union of an intersection of function 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 // * 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. // This type is either never, boolean type, or a boolean singleton.
TypeId booleans; TypeId booleans;
NormalizedClassType classes; NormalizedExternType externTypes;
// The error part of the type. // The error part of the type.
// This type is either never or the error 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) // Helpers that improve readability of the above (they just say if the component is present)
bool hasTops() const; bool hasTops() const;
bool hasBooleans() const; bool hasBooleans() const;
bool hasClasses() const; bool hasExternTypes() const;
bool hasErrors() const; bool hasErrors() const;
bool hasNils() const; bool hasNils() const;
bool hasNumbers() const; bool hasNumbers() const;
@ -391,10 +391,10 @@ public:
void unionTysWithTy(TypeIds& here, TypeId there); void unionTysWithTy(TypeIds& here, TypeId there);
TypeId unionOfTops(TypeId here, TypeId there); TypeId unionOfTops(TypeId here, TypeId there);
TypeId unionOfBools(TypeId here, TypeId there); TypeId unionOfBools(TypeId here, TypeId there);
void unionClassesWithClass(TypeIds& heres, TypeId there); void unionExternTypesWithExternType(TypeIds& heres, TypeId there);
void unionClasses(TypeIds& heres, const TypeIds& theres); void unionExternTypes(TypeIds& heres, const TypeIds& theres);
void unionClassesWithClass(NormalizedClassType& heres, TypeId there); void unionExternTypesWithExternType(NormalizedExternType& heres, TypeId there);
void unionClasses(NormalizedClassType& heres, const NormalizedClassType& theres); void unionExternTypes(NormalizedExternType& heres, const NormalizedExternType& theres);
void unionStrings(NormalizedStringType& here, const NormalizedStringType& there); void unionStrings(NormalizedStringType& here, const NormalizedStringType& there);
std::optional<TypePackId> unionOfTypePacks(TypePackId here, TypePackId there); std::optional<TypePackId> unionOfTypePacks(TypePackId here, TypePackId there);
std::optional<TypeId> unionOfFunctions(TypeId here, TypeId there); std::optional<TypeId> unionOfFunctions(TypeId here, TypeId there);
@ -423,8 +423,8 @@ public:
// ------- Normalizing intersections // ------- Normalizing intersections
TypeId intersectionOfTops(TypeId here, TypeId there); TypeId intersectionOfTops(TypeId here, TypeId there);
TypeId intersectionOfBools(TypeId here, TypeId there); TypeId intersectionOfBools(TypeId here, TypeId there);
void intersectClasses(NormalizedClassType& heres, const NormalizedClassType& theres); void intersectExternTypes(NormalizedExternType& heres, const NormalizedExternType& theres);
void intersectClassesWithClass(NormalizedClassType& heres, TypeId there); void intersectExternTypesWithExternType(NormalizedExternType& heres, TypeId there);
void intersectStrings(NormalizedStringType& here, const NormalizedStringType& there); void intersectStrings(NormalizedStringType& here, const NormalizedStringType& there);
std::optional<TypePackId> intersectionOfTypePacks(TypePackId here, TypePackId there); std::optional<TypePackId> intersectionOfTypePacks(TypePackId here, TypePackId there);
std::optional<TypeId> intersectionOfTables(TypeId here, TypeId there, SeenTablePropPairs& seenTablePropPairs, Set<TypeId>& seenSet); 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); 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. // implements. We could, and perhaps should, merge them together.
template<typename K, typename V> template<typename K, typename V>
struct OrderedMap struct OrderedMap

View file

@ -24,6 +24,9 @@ SimplifyResult simplifyIntersection(NotNull<BuiltinTypes> builtinTypes, NotNull<
SimplifyResult simplifyUnion(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, TypeId left, TypeId right); SimplifyResult simplifyUnion(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, TypeId left, TypeId right);
SimplifyResult simplifyIntersectWithTruthy(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, TypeId target);
SimplifyResult simplifyIntersectWithFalsy(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, TypeId target);
enum class Relation enum class Relation
{ {
Disjoint, // No A is a B or vice versa Disjoint, // No A is a B or vice versa

View file

@ -86,6 +86,7 @@ struct TarjanNode
struct Tarjan struct Tarjan
{ {
Tarjan(); Tarjan();
virtual ~Tarjan() = default;
// Vertices (types and type packs) are indexed, using pre-order traversal. // Vertices (types and type packs) are indexed, using pre-order traversal.
DenseHashMap<TypeId, int> typeToIndex{nullptr}; DenseHashMap<TypeId, int> typeToIndex{nullptr};
@ -121,7 +122,7 @@ struct Tarjan
void visitChildren(TypePackId tp, int index); void visitChildren(TypePackId tp, int index);
void visitChild(TypeId ty); void visitChild(TypeId ty);
void visitChild(TypePackId ty); void visitChild(TypePackId tp);
template<typename Ty> template<typename Ty>
void visitChild(std::optional<Ty> ty) void visitChild(std::optional<Ty> ty)
@ -132,7 +133,7 @@ struct Tarjan
// Visit the root vertex. // Visit the root vertex.
TarjanResult visitRoot(TypeId ty); TarjanResult visitRoot(TypeId ty);
TarjanResult visitRoot(TypePackId ty); TarjanResult visitRoot(TypePackId tp);
// Used to reuse the object for a new operation // Used to reuse the object for a new operation
void clearTarjan(const TxnLog* log); void clearTarjan(const TxnLog* log);
@ -150,26 +151,12 @@ struct Tarjan
void visitSCC(int index); void visitSCC(int index);
// Each subclass can decide to ignore some nodes. // Each subclass can decide to ignore some nodes.
virtual bool ignoreChildren(TypeId ty) virtual bool ignoreChildren(TypeId ty);
{ virtual bool ignoreChildren(TypePackId ty);
return false;
}
virtual bool ignoreChildren(TypePackId ty)
{
return false;
}
// Some subclasses might ignore children visit, but not other actions like replacing the children // Some subclasses might ignore children visit, but not other actions like replacing the children
virtual bool ignoreChildrenVisit(TypeId ty) virtual bool ignoreChildrenVisit(TypeId ty);
{ virtual bool ignoreChildrenVisit(TypePackId ty);
return ignoreChildren(ty);
}
virtual bool ignoreChildrenVisit(TypePackId ty)
{
return ignoreChildren(ty);
}
// Subclasses should say which vertices are dirty, // Subclasses should say which vertices are dirty,
// and what to do with dirty vertices. // and what to do with dirty vertices.
@ -184,6 +171,7 @@ struct Tarjan
struct Substitution : Tarjan struct Substitution : Tarjan
{ {
protected: protected:
explicit Substitution(TypeArena* arena);
Substitution(const TxnLog* log_, TypeArena* arena); Substitution(const TxnLog* log_, TypeArena* arena);
/* /*
@ -232,28 +220,23 @@ public:
virtual TypeId clean(TypeId ty) = 0; virtual TypeId clean(TypeId ty) = 0;
virtual TypePackId clean(TypePackId tp) = 0; virtual TypePackId clean(TypePackId tp) = 0;
protected:
// Helper functions to create new types (used by subclasses) // Helper functions to create new types (used by subclasses)
template<typename T> template<typename T>
TypeId addType(const T& tv) TypeId addType(T tv)
{ {
return arena->addType(tv); return arena->addType(std::move(tv));
} }
template<typename T> template<typename T>
TypePackId addTypePack(const T& tp) TypePackId addTypePack(T tp)
{ {
return arena->addTypePack(TypePackVar{tp}); return arena->addTypePack(TypePackVar{std::move(tp)});
} }
private: private:
template<typename Ty> template<typename Ty>
std::optional<Ty> replace(std::optional<Ty> ty) std::optional<Ty> replace(std::optional<Ty> ty);
{
if (ty)
return replace(*ty);
else
return std::nullopt;
}
}; };
} // namespace Luau } // namespace Luau

View file

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

View file

@ -44,6 +44,7 @@ struct ToStringOptions
bool hideTableKind = false; // If true, all tables will be surrounded with plain '{}' 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 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 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. 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 maxTableLength = size_t(FInt::LuauTableTypeMaximumStringifierLength); // Only applied to TableTypes
size_t maxTypeLength = size_t(FInt::LuauTypeMaximumStringifierLength); size_t maxTypeLength = size_t(FInt::LuauTypeMaximumStringifierLength);

View file

@ -74,10 +74,6 @@ struct FreeType
// This one got promoted to explicit // This one got promoted to explicit
explicit FreeType(Scope* scope, TypeId lowerBound, TypeId upperBound, Polarity polarity = Polarity::Unknown); explicit FreeType(Scope* scope, TypeId lowerBound, TypeId upperBound, Polarity polarity = Polarity::Unknown);
explicit FreeType(Scope* scope, TypeLevel level, TypeId lowerBound, TypeId upperBound); 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; int index;
TypeLevel level; TypeLevel level;
@ -291,7 +287,7 @@ struct MagicFunctionCallContext
{ {
NotNull<struct ConstraintSolver> solver; NotNull<struct ConstraintSolver> solver;
NotNull<const Constraint> constraint; NotNull<const Constraint> constraint;
const class AstExprCall* callSite; NotNull<const AstExprCall> callSite;
TypePackId arguments; TypePackId arguments;
TypePackId result; TypePackId result;
}; };
@ -536,15 +532,15 @@ struct ClassUserData
virtual ~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. * The properties of a class are always exactly known.
* Classes optionally have a parent class. * Extern types optionally have a parent type.
* Two different classes that share the same properties are nevertheless distinct and mutually incompatible. * Two different extern types that share the same properties are nevertheless distinct and mutually incompatible.
*/ */
struct ClassType struct ExternType
{ {
using Props = TableType::Props; using Props = TableType::Props;
@ -558,7 +554,7 @@ struct ClassType
std::optional<Location> definitionLocation; std::optional<Location> definitionLocation;
std::optional<TableIndexer> indexer; std::optional<TableIndexer> indexer;
ClassType( ExternType(
Name name, Name name,
Props props, Props props,
std::optional<TypeId> parent, std::optional<TypeId> parent,
@ -579,7 +575,7 @@ struct ClassType
{ {
} }
ClassType( ExternType(
Name name, Name name,
Props props, Props props,
std::optional<TypeId> parent, std::optional<TypeId> parent,
@ -779,7 +775,7 @@ using TypeVariant = Unifiable::Variant<
FunctionType, FunctionType,
TableType, TableType,
MetatableType, MetatableType,
ClassType, ExternType,
AnyType, AnyType,
UnionType, UnionType,
IntersectionType, IntersectionType,
@ -990,7 +986,7 @@ public:
const TypeId threadType; const TypeId threadType;
const TypeId bufferType; const TypeId bufferType;
const TypeId functionType; const TypeId functionType;
const TypeId classType; const TypeId externType;
const TypeId tableType; const TypeId tableType;
const TypeId emptyTableType; const TypeId emptyTableType;
const TypeId trueType; const TypeId trueType;
@ -1002,6 +998,7 @@ public:
const TypeId noRefineType; const TypeId noRefineType;
const TypeId falsyType; const TypeId falsyType;
const TypeId truthyType; const TypeId truthyType;
const TypeId notNilType;
const TypeId optionalNumberType; const TypeId optionalNumberType;
const TypeId optionalStringType; const TypeId optionalStringType;
@ -1022,10 +1019,10 @@ TypeLevel* getMutableLevel(TypeId ty);
std::optional<TypeLevel> getLevel(TypePackId tp); 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` // 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); 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);
TypeId freshType(NotNull<BuiltinTypes> builtins, Scope* scope, TypeLevel level); 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 freshTypePack(Scope* scope, Polarity polarity = Polarity::Unknown);
TypePackId addTypePack(std::initializer_list<TypeId> types); TypePackId addTypePack(std::initializer_list<TypeId> types);

View file

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

View file

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

View file

@ -155,6 +155,9 @@ struct TypeFunction
/// The reducer function for the type function. /// The reducer function for the type function.
ReducerFunction<TypeId> reducer; ReducerFunction<TypeId> reducer;
/// If true, this type function can reduce even if it is parameterized on a generic.
bool canReduceGenerics = false;
}; };
/// Represents a type function that may be applied to map a series of types and /// Represents a type function that may be applied to map a series of types and
@ -167,6 +170,9 @@ struct TypePackFunction
/// The reducer function for the type pack function. /// The reducer function for the type pack function.
ReducerFunction<TypePackId> reducer; ReducerFunction<TypePackId> reducer;
/// If true, this type function can reduce even if it is parameterized on a generic.
bool canReduceGenerics = false;
}; };
struct FunctionGraphReductionResult struct FunctionGraphReductionResult

View file

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

View file

@ -29,7 +29,7 @@ struct SingletonType;
struct FunctionType; struct FunctionType;
struct TableType; struct TableType;
struct MetatableType; struct MetatableType;
struct ClassType; struct ExternType;
struct AnyType; struct AnyType;
struct UnionType; struct UnionType;
struct IntersectionType; 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, TypeId ty, const ScopePtr& funScope, const AstStatLocalFunction& function);
ControlFlow check(const ScopePtr& scope, const AstStatTypeAlias& typealias); ControlFlow check(const ScopePtr& scope, const AstStatTypeAlias& typealias);
ControlFlow check(const ScopePtr& scope, const AstStatTypeFunction& typefunction); 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); 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 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 checkBlock(const ScopePtr& scope, const AstStatBlock& statement);
ControlFlow checkBlockWithoutRecursionCheck(const ScopePtr& scope, const AstStatBlock& statement); ControlFlow checkBlockWithoutRecursionCheck(const ScopePtr& scope, const AstStatBlock& statement);
@ -130,6 +130,7 @@ struct TypeChecker
const PredicateVec& predicates = {} const PredicateVec& predicates = {}
); );
WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprBinary& expr, std::optional<TypeId> expectedType = std::nullopt); WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprBinary& expr, std::optional<TypeId> expectedType = std::nullopt);
WithPredicate<TypeId> checkExpr_DEPRECATED(const ScopePtr& scope, const AstExprBinary& expr, std::optional<TypeId> expectedType = std::nullopt);
WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprTypeAssertion& expr); WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprTypeAssertion& expr);
WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprError& expr); WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprError& expr);
WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprIfElse& expr, std::optional<TypeId> expectedType = std::nullopt); WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprIfElse& expr, std::optional<TypeId> expectedType = std::nullopt);
@ -486,7 +487,7 @@ private:
/** /**
* A set of incorrect class definitions which is used to avoid a second-pass analysis. * 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; 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 begin(TypePackId tp, const TxnLog* log);
TypePackIterator end(TypePackId tp); TypePackIterator end(TypePackId tp);
TypePackId getTail(TypePackId tp);
using SeenSet = std::set<std::pair<const void*, const void*>>; using SeenSet = std::set<std::pair<const void*, const void*>>;
bool areEqual(SeenSet& seen, const TypePackVar& lhs, const TypePackVar& rhs); 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 tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection = false, const LiteralProperties* aliasableMap = nullptr);
void tryUnifyScalarShape(TypeId subTy, TypeId superTy, bool reversed); void tryUnifyScalarShape(TypeId subTy, TypeId superTy, bool reversed);
void tryUnifyWithMetatable(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); void tryUnifyNegations(TypeId subTy, TypeId superTy);
TypePackId tryApplyOverloadedFunction(TypeId function, const NormalizedFunctionType& overloads, TypePackId args); TypePackId tryApplyOverloadedFunction(TypeId function, const NormalizedFunctionType& overloads, TypePackId args);

View file

@ -126,7 +126,7 @@ struct GenericTypeVisitor
{ {
return visit(ty); return visit(ty);
} }
virtual bool visit(TypeId ty, const ClassType& ctv) virtual bool visit(TypeId ty, const ExternType& etv)
{ {
return visit(ty); return visit(ty);
} }
@ -313,11 +313,11 @@ struct GenericTypeVisitor
traverse(mtv->metatable); 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) if (FFlag::LuauSolverV2)
{ {
@ -335,16 +335,16 @@ struct GenericTypeVisitor
traverse(prop.type()); traverse(prop.type());
} }
if (ctv->parent) if (etv->parent)
traverse(*ctv->parent); traverse(*etv->parent);
if (ctv->metatable) if (etv->metatable)
traverse(*ctv->metatable); traverse(*etv->metatable);
if (ctv->indexer) if (etv->indexer)
{ {
traverse(ctv->indexer->indexType); traverse(etv->indexer->indexType);
traverse(ctv->indexer->indexResultType); traverse(etv->indexer->indexResultType);
} }
} }
} }
@ -396,7 +396,7 @@ struct GenericTypeVisitor
traverse(unwrapped); traverse(unwrapped);
// Visiting into LazyType that hasn't been unwrapped may necessarily cause infinite expansion, so we don't do that on purpose. // 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. // that doesn't need to be expanded.
} }
else if (auto stv = get<SingletonType>(ty)) else if (auto stv = get<SingletonType>(ty))

View file

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

View file

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

View file

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

View file

@ -574,11 +574,11 @@ std::optional<DocumentationSymbol> getDocumentationSymbolAtPosition(const Source
return checkOverloadedDocumentationSymbol(module, propIt->second.type(), parentExpr, propIt->second.documentationSymbol); 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) if (FFlag::LuauSolverV2)
{ {
@ -590,7 +590,7 @@ std::optional<DocumentationSymbol> getDocumentationSymbolAtPosition(const Source
module, propIt->second.type(), parentExpr, propIt->second.documentationSymbol 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) else if (const PrimitiveType* ptv = get<PrimitiveType>(parentTy); ptv && ptv->metatable)

View file

@ -24,13 +24,10 @@ LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTINT(LuauTypeInferIterationLimit) LUAU_FASTINT(LuauTypeInferIterationLimit)
LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAGVARIABLE(DebugLuauMagicVariableNames) LUAU_FASTFLAGVARIABLE(DebugLuauMagicVariableNames)
LUAU_FASTFLAG(LuauExposeRequireByStringAutocomplete)
LUAU_FASTFLAGVARIABLE(LuauAutocompleteRefactorsForIncrementalAutocomplete)
LUAU_FASTFLAGVARIABLE(LuauAutocompleteUsesModuleForTypeCompatibility) LUAU_FASTFLAGVARIABLE(LuauAutocompleteUsesModuleForTypeCompatibility)
LUAU_FASTFLAGVARIABLE(LuauAutocompleteUnionCopyPreviousSeen) LUAU_FASTFLAGVARIABLE(LuauAutocompleteUnionCopyPreviousSeen)
LUAU_FASTFLAGVARIABLE(LuauAutocompleteMissingFollows)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
static const std::unordered_set<std::string> kStatementStartingKeywords = static const std::unordered_set<std::string> kStatementStartingKeywords =
{"while", "if", "local", "repeat", "function", "do", "for", "return", "break", "continue", "type", "export"}; {"while", "if", "local", "repeat", "function", "do", "for", "return", "break", "continue", "type", "export"};
@ -83,6 +80,8 @@ static ParenthesesRecommendation getParenRecommendationForIntersect(const Inters
ParenthesesRecommendation rec = ParenthesesRecommendation::None; ParenthesesRecommendation rec = ParenthesesRecommendation::None;
for (Luau::TypeId partId : intersect->parts) for (Luau::TypeId partId : intersect->parts)
{ {
if (FFlag::LuauAutocompleteMissingFollows)
partId = follow(partId);
if (auto partFunc = Luau::get<FunctionType>(partId)) if (auto partFunc = Luau::get<FunctionType>(partId))
{ {
rec = std::max(rec, getParenRecommendationForFunc(partFunc, nodes)); rec = std::max(rec, getParenRecommendationForFunc(partFunc, nodes));
@ -308,7 +307,7 @@ static void autocompleteProps(
const std::vector<AstNode*>& nodes, const std::vector<AstNode*>& nodes,
AutocompleteEntryMap& result, AutocompleteEntryMap& result,
std::unordered_set<TypeId>& seen, std::unordered_set<TypeId>& seen,
std::optional<const ClassType*> containingClass = std::nullopt std::optional<const ExternType*> containingExternType = std::nullopt
) )
{ {
rootTy = follow(rootTy); rootTy = follow(rootTy);
@ -331,8 +330,8 @@ static void autocompleteProps(
if (calledWithSelf == ftv->hasSelf) if (calledWithSelf == ftv->hasSelf)
return true; return true;
// Calls on classes require strict match between how function is declared and how it's called // Calls on extern types require strict match between how function is declared and how it's called
if (get<ClassType>(rootTy)) if (get<ExternType>(rootTy))
return false; return false;
// When called with ':', but declared without 'self', it is invalid if a function has incompatible first argument or no arguments at all // When called with ':', but declared without 'self', it is invalid if a function has incompatible first argument or no arguments at all
@ -365,7 +364,7 @@ static void autocompleteProps(
return calledWithSelf; return calledWithSelf;
}; };
auto fillProps = [&](const ClassType::Props& props) auto fillProps = [&](const ExternType::Props& props)
{ {
for (const auto& [name, prop] : props) for (const auto& [name, prop] : props)
{ {
@ -398,7 +397,7 @@ static void autocompleteProps(
prop.deprecated, prop.deprecated,
isWrongIndexer(type), isWrongIndexer(type),
typeCorrect, typeCorrect,
containingClass, containingExternType,
&prop, &prop,
prop.documentationSymbol, prop.documentationSymbol,
{}, {},
@ -429,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); fillProps(cls->props);
if (cls->parent) 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)) else if (auto tbl = get<TableType>(ty))
fillProps(tbl->props); fillProps(tbl->props);
@ -488,7 +487,7 @@ static void autocompleteProps(
// If we don't do this, and we have the misfortune of receiving a // If we don't do this, and we have the misfortune of receiving a
// recursive union like: // 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. // Then we are on a one way journey to a stack overflow.
if (FFlag::LuauAutocompleteUnionCopyPreviousSeen) if (FFlag::LuauAutocompleteUnionCopyPreviousSeen)
@ -588,7 +587,7 @@ AutocompleteEntryMap autocompleteProps(
AutocompleteEntryMap autocompleteModuleTypes(const Module& module, const ScopePtr& scopeAtPosition, Position position, std::string_view moduleName) AutocompleteEntryMap autocompleteModuleTypes(const Module& module, const ScopePtr& scopeAtPosition, Position position, std::string_view moduleName)
{ {
AutocompleteEntryMap result; AutocompleteEntryMap result;
ScopePtr startScope = FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete ? scopeAtPosition : findScopeAtPosition(module, position); ScopePtr startScope = scopeAtPosition;
for (ScopePtr& scope = startScope; scope; scope = scope->parent) for (ScopePtr& scope = startScope; scope; scope = scope->parent)
{ {
if (auto it = scope->importedTypeBindings.find(std::string(moduleName)); it != scope->importedTypeBindings.end()) if (auto it = scope->importedTypeBindings.find(std::string(moduleName)); it != scope->importedTypeBindings.end())
@ -700,6 +699,30 @@ static std::optional<TypeId> findTypeElementAt(const AstTypeList& astTypeList, T
return {}; 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) static std::optional<TypeId> findTypeElementAt(AstType* astType, TypeId ty, Position position)
{ {
ty = follow(ty); ty = follow(ty);
@ -720,9 +743,17 @@ static std::optional<TypeId> findTypeElementAt(AstType* astType, TypeId ty, Posi
if (auto element = findTypeElementAt(type->argTypes, ftv->argTypes, position)) if (auto element = findTypeElementAt(type->argTypes, ftv->argTypes, position))
return element; return element;
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
if (auto element = findTypeElementAt(type->returnTypes, ftv->retTypes, position)) if (auto element = findTypeElementAt(type->returnTypes, ftv->retTypes, position))
return element; return element;
} }
else
{
if (auto element = findTypeElementAt(type->returnTypes_DEPRECATED, ftv->retTypes, position))
return element;
}
}
// It's possible to walk through other types like intrsection and unions if we find value in doing that // It's possible to walk through other types like intrsection and unions if we find value in doing that
return {}; return {};
@ -730,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) 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) for (const auto& [name, binding] : scope->bindings)
{ {
@ -872,7 +903,7 @@ AutocompleteEntryMap autocompleteTypeNames(
{ {
AutocompleteEntryMap result; AutocompleteEntryMap result;
ScopePtr startScope = FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete ? scopeAtPosition : findScopeAtPosition(module, position); ScopePtr startScope = scopeAtPosition;
for (ScopePtr scope = startScope; scope; scope = scope->parent) for (ScopePtr scope = startScope; scope; scope = scope->parent)
{ {
@ -1051,12 +1082,16 @@ AutocompleteEntryMap autocompleteTypeNames(
} }
} }
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
if (!node->returnAnnotation) if (!node->returnAnnotation)
return result; return result;
for (size_t i = 0; i < node->returnAnnotation->types.size; i++) if (const auto typePack = node->returnAnnotation->as<AstTypePackExplicit>())
{ {
AstType* ret = node->returnAnnotation->types.data[i]; for (size_t i = 0; i < typePack->typeList.types.size; i++)
{
AstType* ret = typePack->typeList.types.data[i];
if (ret->location.containsClosed(position)) if (ret->location.containsClosed(position))
{ {
@ -1071,7 +1106,7 @@ AutocompleteEntryMap autocompleteTypeNames(
} }
} }
if (AstTypePack* retTp = node->returnAnnotation->tailType) if (AstTypePack* retTp = typePack->typeList.tailType)
{ {
if (auto variadic = retTp->as<AstTypePackVariadic>()) if (auto variadic = retTp->as<AstTypePackVariadic>())
{ {
@ -1086,6 +1121,56 @@ AutocompleteEntryMap autocompleteTypeNames(
} }
} }
} }
else if (auto variadic = node->returnAnnotation->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);
}
}
}
}
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; return result;
} }
@ -1205,7 +1290,7 @@ static AutocompleteEntryMap autocompleteStatement(
) )
{ {
// This is inefficient. :( // This is inefficient. :(
ScopePtr scope = FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete ? scopeAtPosition : findScopeAtPosition(module, position); ScopePtr scope = scopeAtPosition;
AutocompleteEntryMap result; AutocompleteEntryMap result;
@ -1383,7 +1468,7 @@ static AutocompleteContext autocompleteExpression(
else else
{ {
// This is inefficient. :( // This is inefficient. :(
ScopePtr scope = FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete ? scopeAtPosition : findScopeAtPosition(module, position); ScopePtr scope = scopeAtPosition;
while (scope) while (scope)
{ {
@ -1452,7 +1537,7 @@ static AutocompleteResult autocompleteExpression(
return {result, ancestry, context}; 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; AstExpr* parentExpr = nullptr;
if (auto indexName = funcExpr->as<AstExprIndexName>()) if (auto indexName = funcExpr->as<AstExprIndexName>())
@ -1476,14 +1561,14 @@ static std::optional<const ClassType*> getMethodContainingClass(const ModulePtr&
Luau::TypeId parentType = Luau::follow(*parentIt); 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)) if (auto parentUnion = Luau::get<UnionType>(parentType))
{ {
return returnFirstNonnullOptionOfType<ClassType>(parentUnion); return returnFirstNonnullOptionOfType<ExternType>(parentUnion);
} }
return std::nullopt; return std::nullopt;
@ -1541,10 +1626,7 @@ static std::optional<AutocompleteEntryMap> convertRequireSuggestionsToAutocomple
{ {
AutocompleteEntry entry = {AutocompleteEntryKind::RequirePath}; AutocompleteEntry entry = {AutocompleteEntryKind::RequirePath};
entry.insertText = std::move(suggestion.fullPath); 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); result[std::move(suggestion.label)] = std::move(entry);
} }
return result; return result;
@ -1605,7 +1687,7 @@ static std::optional<AutocompleteEntryMap> autocompleteStringParams(
{ {
return convertRequireSuggestionsToAutocompleteEntryMap(fileResolver->getRequireSuggestions(module->name, candidateString)); 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; return ret;
} }
@ -1623,6 +1705,8 @@ static std::optional<AutocompleteEntryMap> autocompleteStringParams(
{ {
for (TypeId part : intersect->parts) for (TypeId part : intersect->parts)
{ {
if (FFlag::LuauAutocompleteMissingFollows)
part = follow(part);
if (auto candidateFunctionType = Luau::get<FunctionType>(part)) if (auto candidateFunctionType = Luau::get<FunctionType>(part))
{ {
if (std::optional<AutocompleteEntryMap> ret = performCallback(candidateFunctionType)) if (std::optional<AutocompleteEntryMap> ret = performCallback(candidateFunctionType))
@ -1771,7 +1855,7 @@ static std::optional<AutocompleteEntry> makeAnonymousAutofilled(
if (!type) if (!type)
return std::nullopt; return std::nullopt;
const ScopePtr scope = FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete ? scopeAtPosition : findScopeAtPosition(*module, position); const ScopePtr scope = scopeAtPosition;
if (!scope) if (!scope)
return std::nullopt; return std::nullopt;

View file

@ -30,12 +30,11 @@
*/ */
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauNonReentrantGeneralization) LUAU_FASTFLAG(LuauNonReentrantGeneralization2)
LUAU_FASTFLAGVARIABLE(LuauTableCloneClonesType3) LUAU_FASTFLAGVARIABLE(LuauTableCloneClonesType3)
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope)
LUAU_FASTFLAGVARIABLE(LuauFollowTableFreeze)
LUAU_FASTFLAGVARIABLE(LuauUserTypeFunTypecheck) LUAU_FASTFLAGVARIABLE(LuauUserTypeFunTypecheck)
LUAU_FASTFLAGVARIABLE(LuauMagicFreezeCheckBlocked) LUAU_FASTFLAGVARIABLE(LuauMagicFreezeCheckBlocked)
LUAU_FASTFLAGVARIABLE(LuauFormatUseLastPosition)
namespace Luau namespace Luau
{ {
@ -314,8 +313,8 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
TypeArena& arena = globals.globalTypes; TypeArena& arena = globals.globalTypes;
NotNull<BuiltinTypes> builtinTypes = globals.builtinTypes; NotNull<BuiltinTypes> builtinTypes = globals.builtinTypes;
Scope* globalScope = nullptr; // NotNull<Scope> when removing FFlag::LuauNonReentrantGeneralization Scope* globalScope = nullptr; // NotNull<Scope> when removing FFlag::LuauNonReentrantGeneralization2
if (FFlag::LuauNonReentrantGeneralization) if (FFlag::LuauNonReentrantGeneralization2)
globalScope = globals.globalScope.get(); globalScope = globals.globalScope.get();
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
@ -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()) if (auto it = globals.globalScope->exportedTypeBindings.find("vector"); it != globals.globalScope->exportedTypeBindings.end())
{ {
TypeId vectorTy = it->second.type; 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}); vectorCls->metatable = arena.addType(TableType{{}, std::nullopt, TypeLevel{}, TableState::Sealed});
TableType* metatableTy = Luau::getMutable<TableType>(vectorCls->metatable); TableType* metatableTy = Luau::getMutable<TableType>(vectorCls->metatable);
@ -705,6 +704,14 @@ bool MagicFormat::typeCheck(const MagicFunctionTypeCheckContext& context)
return true; 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); std::vector<TypeId> expected = parseFormatString(context.builtinTypes, fmt->value.data, fmt->value.size);
const auto& [params, tail] = flatten(context.arguments); const auto& [params, tail] = flatten(context.arguments);
@ -716,7 +723,9 @@ bool MagicFormat::typeCheck(const MagicFunctionTypeCheckContext& context)
{ {
TypeId actualTy = params[i + paramOffset]; TypeId actualTy = params[i + paramOffset];
TypeId expectedTy = expected[i]; 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 // use subtyping instead here
SubtypingResult result = context.typechecker->subtyping->isSubtype(actualTy, expectedTy, context.checkScope); SubtypingResult result = context.typechecker->subtyping->isSubtype(actualTy, expectedTy, context.checkScope);
@ -1529,7 +1538,6 @@ bool MagicClone::infer(const MagicFunctionCallContext& context)
tableType->scope = context.constraint->scope.get(); 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}); TypePackId clonedTypePack = arena->addTypePack({resultType});
@ -1541,7 +1549,6 @@ bool MagicClone::infer(const MagicFunctionCallContext& context)
static std::optional<TypeId> freezeTable(TypeId inputType, const MagicFunctionCallContext& context) static std::optional<TypeId> freezeTable(TypeId inputType, const MagicFunctionCallContext& context)
{ {
TypeArena* arena = context.solver->arena; TypeArena* arena = context.solver->arena;
if (FFlag::LuauFollowTableFreeze)
inputType = follow(inputType); inputType = follow(inputType);
if (auto mt = get<MetatableType>(inputType)) if (auto mt = get<MetatableType>(inputType))
{ {

View file

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

View file

@ -20,7 +20,7 @@ struct ReferenceCountInitializer : TypeOnceVisitor
DenseHashSet<TypeId>* result; DenseHashSet<TypeId>* result;
ReferenceCountInitializer(DenseHashSet<TypeId>* result) explicit ReferenceCountInitializer(DenseHashSet<TypeId>* result)
: result(result) : result(result)
{ {
} }
@ -43,24 +43,15 @@ struct ReferenceCountInitializer : TypeOnceVisitor
return false; 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; return false;
} }
bool visit(TypeId, const TypeFunctionInstanceType&) override bool visit(TypeId, const TypeFunctionInstanceType&) override
{ {
// We do not consider reference counted types that are inside a type return FFlag::DebugLuauGreedyGeneralization;
// 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;
} }
}; };
@ -130,8 +121,10 @@ DenseHashSet<TypeId> Constraint::getMaybeMutatedFreeTypes() const
} }
else if (auto hic = get<HasIndexerConstraint>(*this)) else if (auto hic = get<HasIndexerConstraint>(*this))
{ {
if (FFlag::DebugLuauGreedyGeneralization)
rci.traverse(hic->subjectType);
rci.traverse(hic->resultType); rci.traverse(hic->resultType);
// `HasIndexerConstraint` should not mutate `subjectType` or `indexType`. // `HasIndexerConstraint` should not mutate `indexType`.
} }
else if (auto apc = get<AssignPropConstraint>(*this)) else if (auto apc = get<AssignPropConstraint>(*this))
{ {
@ -150,6 +143,10 @@ DenseHashSet<TypeId> Constraint::getMaybeMutatedFreeTypes() const
rci.traverse(ty); rci.traverse(ty);
// `UnpackConstraint` should not mutate `sourcePack`. // `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)) else if (auto rpc = get<ReducePackConstraint>(*this))
{ {
rci.traverse(rpc->tp); rci.traverse(rpc->tp);

View file

@ -33,19 +33,12 @@
LUAU_FASTINT(LuauCheckRecursionLimit) LUAU_FASTINT(LuauCheckRecursionLimit)
LUAU_FASTFLAG(DebugLuauLogSolverToJson) LUAU_FASTFLAG(DebugLuauLogSolverToJson)
LUAU_FASTFLAG(DebugLuauMagicTypes) LUAU_FASTFLAG(DebugLuauMagicTypes)
LUAU_FASTFLAG(LuauNonReentrantGeneralization) LUAU_FASTFLAG(LuauNonReentrantGeneralization2)
LUAU_FASTFLAG(LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
LUAU_FASTFLAGVARIABLE(LuauPropagateExpectedTypesForCalls) LUAU_FASTFLAGVARIABLE(LuauPropagateExpectedTypesForCalls)
LUAU_FASTFLAG(DebugLuauGreedyGeneralization) LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
LUAU_FASTFLAGVARIABLE(LuauTrackInteriorFreeTypesOnScope)
LUAU_FASTFLAGVARIABLE(LuauUngeneralizedTypesForRecursiveFunctions) LUAU_FASTFLAGVARIABLE(LuauUngeneralizedTypesForRecursiveFunctions)
LUAU_FASTFLAGVARIABLE(LuauGlobalSelfAssignmentCycle)
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
LUAU_FASTFLAGVARIABLE(LuauInferLocalTypesInMultipleAssignments)
LUAU_FASTFLAGVARIABLE(LuauDoNotLeakNilInRefinement)
LUAU_FASTFLAGVARIABLE(LuauExtraFollows)
LUAU_FASTFLAG(LuauUserTypeFunTypecheck) LUAU_FASTFLAG(LuauUserTypeFunTypecheck)
LUAU_FASTFLAGVARIABLE(LuauRetainDefinitionAliasLocations) LUAU_FASTFLAGVARIABLE(LuauRetainDefinitionAliasLocations)
@ -53,6 +46,12 @@ LUAU_FASTFLAG(LuauDeprecatedAttribute)
LUAU_FASTFLAGVARIABLE(LuauCacheInferencePerAstExpr) LUAU_FASTFLAGVARIABLE(LuauCacheInferencePerAstExpr)
LUAU_FASTFLAGVARIABLE(LuauAlwaysResolveAstTypes) LUAU_FASTFLAGVARIABLE(LuauAlwaysResolveAstTypes)
LUAU_FASTFLAGVARIABLE(LuauWeakNilRefinementType) LUAU_FASTFLAGVARIABLE(LuauWeakNilRefinementType)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
LUAU_FASTFLAG(LuauGlobalVariableModuleIsolation)
LUAU_FASTFLAGVARIABLE(LuauNoTypeFunctionsNamedTypeOf)
LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions)
LUAU_FASTFLAG(LuauDoNotAddUpvalueTypesToLocalType)
namespace Luau namespace Luau
{ {
@ -157,7 +156,7 @@ struct HasFreeType : TypeOnceVisitor
return true; return true;
} }
bool visit(TypeId ty, const ClassType&) override bool visit(TypeId ty, const ExternType&) override
{ {
return false; return false;
} }
@ -218,6 +217,32 @@ ConstraintGenerator::ConstraintGenerator(
LUAU_ASSERT(module); 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) void ConstraintGenerator::visitModuleRoot(AstStatBlock* block)
{ {
LUAU_TIMETRACE_SCOPE("ConstraintGenerator::visitModuleRoot", "Typechecking"); LUAU_TIMETRACE_SCOPE("ConstraintGenerator::visitModuleRoot", "Typechecking");
@ -230,7 +255,7 @@ void ConstraintGenerator::visitModuleRoot(AstStatBlock* block)
rootScope->location = block->location; rootScope->location = block->location;
module->astScopes[block] = NotNull{scope.get()}; module->astScopes[block] = NotNull{scope.get()};
if (FFlag::LuauNonReentrantGeneralization) if (FFlag::LuauNonReentrantGeneralization2)
interiorFreeTypes.emplace_back(); interiorFreeTypes.emplace_back();
else else
DEPRECATED_interiorTypes.emplace_back(); DEPRECATED_interiorTypes.emplace_back();
@ -263,17 +288,16 @@ void ConstraintGenerator::visitModuleRoot(AstStatBlock* block)
GeneralizationConstraint{ GeneralizationConstraint{
result, result,
moduleFnTy, moduleFnTy,
(FFlag::LuauNonReentrantGeneralization || FFlag::LuauTrackInteriorFreeTypesOnScope) ? std::vector<TypeId>{} std::vector<TypeId>{},
: std::move(DEPRECATED_interiorTypes.back())
} }
); );
if (FFlag::LuauNonReentrantGeneralization) if (FFlag::LuauNonReentrantGeneralization2)
{ {
scope->interiorFreeTypes = std::move(interiorFreeTypes.back().types); scope->interiorFreeTypes = std::move(interiorFreeTypes.back().types);
scope->interiorFreeTypePacks = std::move(interiorFreeTypes.back().typePacks); scope->interiorFreeTypePacks = std::move(interiorFreeTypes.back().typePacks);
} }
else if (FFlag::LuauTrackInteriorFreeTypesOnScope) else
scope->interiorFreeTypes = std::move(DEPRECATED_interiorTypes.back()); scope->interiorFreeTypes = std::move(DEPRECATED_interiorTypes.back());
getMutable<BlockedType>(result)->setOwner(genConstraint); getMutable<BlockedType>(result)->setOwner(genConstraint);
@ -287,7 +311,7 @@ void ConstraintGenerator::visitModuleRoot(AstStatBlock* block)
} }
); );
if (FFlag::LuauNonReentrantGeneralization) if (FFlag::LuauNonReentrantGeneralization2)
interiorFreeTypes.pop_back(); interiorFreeTypes.pop_back();
else else
DEPRECATED_interiorTypes.pop_back(); DEPRECATED_interiorTypes.pop_back();
@ -319,13 +343,13 @@ void ConstraintGenerator::visitFragmentRoot(const ScopePtr& resumeScope, AstStat
// We prepopulate global data in the resumeScope to avoid writing data into the old modules scopes // We prepopulate global data in the resumeScope to avoid writing data into the old modules scopes
prepopulateGlobalScopeForFragmentTypecheck(globalScope, resumeScope, block); prepopulateGlobalScopeForFragmentTypecheck(globalScope, resumeScope, block);
// Pre // Pre
if (FFlag::LuauNonReentrantGeneralization) if (FFlag::LuauNonReentrantGeneralization2)
interiorFreeTypes.emplace_back(); interiorFreeTypes.emplace_back();
else else
DEPRECATED_interiorTypes.emplace_back(); DEPRECATED_interiorTypes.emplace_back();
visitBlockWithoutChildScope(resumeScope, block); visitBlockWithoutChildScope(resumeScope, block);
// Post // Post
if (FFlag::LuauNonReentrantGeneralization) if (FFlag::LuauNonReentrantGeneralization2)
interiorFreeTypes.pop_back(); interiorFreeTypes.pop_back();
else else
DEPRECATED_interiorTypes.pop_back(); DEPRECATED_interiorTypes.pop_back();
@ -355,21 +379,21 @@ void ConstraintGenerator::visitFragmentRoot(const ScopePtr& resumeScope, AstStat
TypeId ConstraintGenerator::freshType(const ScopePtr& scope, Polarity polarity) TypeId ConstraintGenerator::freshType(const ScopePtr& scope, Polarity polarity)
{ {
if (FFlag::LuauNonReentrantGeneralization) if (FFlag::LuauNonReentrantGeneralization2)
{ {
auto ft = Luau::freshType(arena, builtinTypes, scope.get(), polarity); auto ft = Luau::freshType(arena, builtinTypes, scope.get(), polarity);
interiorFreeTypes.back().types.push_back(ft); interiorFreeTypes.back().types.push_back(ft);
return ft;
} if (FFlag::DebugLuauGreedyGeneralization)
else if (FFlag::LuauTrackInteriorFreeTypesOnScope) freeTypes.insert(ft);
{
auto ft = Luau::freshType(arena, builtinTypes, scope.get());
DEPRECATED_interiorTypes.back().push_back(ft);
return ft; return ft;
} }
else else
{ {
return Luau::freshType(arena, builtinTypes, scope.get()); auto ft = Luau::freshType(arena, builtinTypes, scope.get());
DEPRECATED_interiorTypes.back().push_back(ft);
return ft;
} }
} }
@ -377,7 +401,7 @@ TypePackId ConstraintGenerator::freshTypePack(const ScopePtr& scope, Polarity po
{ {
FreeTypePack f{scope.get(), polarity}; FreeTypePack f{scope.get(), polarity};
TypePackId result = arena->addTypePack(TypePackVar{std::move(f)}); TypePackId result = arena->addTypePack(TypePackVar{std::move(f)});
if (FFlag::LuauNonReentrantGeneralization) if (FFlag::LuauNonReentrantGeneralization2)
interiorFreeTypes.back().typePacks.push_back(result); interiorFreeTypes.back().typePacks.push_back(result);
return result; return result;
} }
@ -578,16 +602,9 @@ void ConstraintGenerator::computeRefinement(
// When the top-level expression is `t[x]`, we want to refine it into `nil`, not `never`. // When the top-level expression is `t[x]`, we want to refine it into `nil`, not `never`.
LUAU_ASSERT(refis->get(proposition->key->def)); LUAU_ASSERT(refis->get(proposition->key->def));
if (FFlag::LuauDoNotLeakNilInRefinement)
{
refis->get(proposition->key->def)->shouldAppendNilType = refis->get(proposition->key->def)->shouldAppendNilType =
(sense || !eq) && containsSubscriptedDefinition(proposition->key->def) && !proposition->implicitFromCall; (sense || !eq) && containsSubscriptedDefinition(proposition->key->def) && !proposition->implicitFromCall;
} }
else
{
refis->get(proposition->key->def)->shouldAppendNilType = (sense || !eq) && containsSubscriptedDefinition(proposition->key->def);
}
}
} }
namespace namespace
@ -633,7 +650,7 @@ struct FindSimplificationBlockers : TypeOnceVisitor
return false; return false;
} }
bool visit(TypeId, const ClassType&) override bool visit(TypeId, const ExternType&) override
{ {
return false; return false;
} }
@ -858,7 +875,7 @@ void ConstraintGenerator::checkAliases(const ScopePtr& scope, AstStatBlock* bloc
aliasDefinitionLocations[function->name.value] = function->location; 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)) if (scope->exportedTypeBindings.count(classDeclaration->name.value))
{ {
@ -1052,7 +1069,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStat* stat)
return visit(scope, s); return visit(scope, s);
else if (auto s = stat->as<AstStatDeclareFunction>()) else if (auto s = stat->as<AstStatDeclareFunction>())
return visit(scope, s); return visit(scope, s);
else if (auto s = stat->as<AstStatDeclareClass>()) else if (auto s = stat->as<AstStatDeclareExternType>())
return visit(scope, s); return visit(scope, s);
else if (auto s = stat->as<AstStatError>()) else if (auto s = stat->as<AstStatError>())
return visit(scope, s); return visit(scope, s);
@ -1120,8 +1137,6 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatLocal* stat
TypePackId rvaluePack = checkPack(scope, statLocal->values, expectedTypes).tp; TypePackId rvaluePack = checkPack(scope, statLocal->values, expectedTypes).tp;
Checkpoint end = checkpoint(this); Checkpoint end = checkpoint(this);
if (FFlag::LuauInferLocalTypesInMultipleAssignments)
{
std::vector<TypeId> deferredTypes; std::vector<TypeId> deferredTypes;
auto [head, tail] = flatten(rvaluePack); auto [head, tail] = flatten(rvaluePack);
@ -1177,64 +1192,6 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatLocal* stat
for (TypeId t : deferredTypes) for (TypeId t : deferredTypes)
getMutable<BlockedType>(t)->setOwner(uc); getMutable<BlockedType>(t)->setOwner(uc);
} }
}
else
{
if (hasAnnotation)
{
for (size_t i = 0; i < statLocal->vars.size; ++i)
{
LUAU_ASSERT(get<BlockedType>(assignees[i]));
TypeIds* localDomain = localTypes.find(assignees[i]);
LUAU_ASSERT(localDomain);
localDomain->insert(annotatedTypes[i]);
}
TypePackId annotatedPack = arena->addTypePack(std::move(annotatedTypes));
addConstraint(scope, statLocal->location, PackSubtypeConstraint{rvaluePack, annotatedPack});
}
else
{
std::vector<TypeId> valueTypes;
valueTypes.reserve(statLocal->vars.size);
auto [head, tail] = flatten(rvaluePack);
if (head.size() >= statLocal->vars.size)
{
for (size_t i = 0; i < statLocal->vars.size; ++i)
valueTypes.push_back(head[i]);
}
else
{
for (size_t i = 0; i < statLocal->vars.size; ++i)
valueTypes.push_back(arena->addType(BlockedType{}));
auto uc = addConstraint(scope, statLocal->location, UnpackConstraint{valueTypes, rvaluePack});
forEachConstraint(
start,
end,
this,
[&uc](const ConstraintPtr& runBefore)
{
uc->dependencies.push_back(NotNull{runBefore.get()});
}
);
for (TypeId t : valueTypes)
getMutable<BlockedType>(t)->setOwner(uc);
}
for (size_t i = 0; i < statLocal->vars.size; ++i)
{
LUAU_ASSERT(get<BlockedType>(assignees[i]));
TypeIds* localDomain = localTypes.find(assignees[i]);
LUAU_ASSERT(localDomain);
localDomain->insert(valueTypes[i]);
}
}
}
if (statLocal->vars.size == 1 && statLocal->values.size == 1 && firstValueType && scope.get() == rootScope && !hasAnnotation) if (statLocal->vars.size == 1 && statLocal->values.size == 1 && firstValueType && scope.get() == rootScope && !hasAnnotation)
{ {
@ -1333,11 +1290,8 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatForIn* forI
for (AstLocal* var : forIn->vars) for (AstLocal* var : forIn->vars)
{ {
TypeId assignee = arena->addType(BlockedType{});
variableTypes.push_back(assignee);
TypeId loopVar = arena->addType(BlockedType{}); TypeId loopVar = arena->addType(BlockedType{});
localTypes[loopVar].insert(assignee); variableTypes.push_back(loopVar);
if (var->annotation) if (var->annotation)
{ {
@ -1356,6 +1310,23 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatForIn* forI
loopScope, getLocation(forIn->values), IterableConstraint{iterator, variableTypes, forIn->values.data[0], &module->astForInNextTypes} 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) for (TypeId var : variableTypes)
{ {
auto bt = getMutable<BlockedType>(var); auto bt = getMutable<BlockedType>(var);
@ -1438,7 +1409,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatLocalFuncti
FunctionSignature sig = checkFunctionSignature(scope, function->func, /* expectedType */ std::nullopt, function->name->location); FunctionSignature sig = checkFunctionSignature(scope, function->func, /* expectedType */ std::nullopt, function->name->location);
sig.bodyScope->bindings[function->name] = Binding{sig.signature, 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) if (sigFullyDefined)
emplaceType<BoundType>(asMutable(functionType), sig.signature); emplaceType<BoundType>(asMutable(functionType), sig.signature);
@ -1500,7 +1471,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFunction* f
Checkpoint start = checkpoint(this); Checkpoint start = checkpoint(this);
FunctionSignature sig = checkFunctionSignature(scope, function->func, /* expectedType */ std::nullopt, function->name->location); 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); DefId def = dfg->getDef(function->name);
@ -1747,7 +1718,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeAlias*
if (alias->name == "typeof") if (alias->name == "typeof")
{ {
reportError(alias->location, GenericError{"Type aliases cannot be named typeof"}); reportError(alias->location, ReservedIdentifier{"typeof"});
return ControlFlow::None; return ControlFlow::None;
} }
@ -1808,6 +1779,14 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeFunctio
if (!FFlag::LuauUserTypeFunTypecheck) if (!FFlag::LuauUserTypeFunTypecheck)
return ControlFlow::None; return ControlFlow::None;
if (FFlag::LuauNoTypeFunctionsNamedTypeOf)
{
if (function->name == "typeof")
{
reportError(function->location, ReservedIdentifier{"typeof"});
}
}
auto scopePtr = astTypeFunctionEnvironmentScopes.find(function); auto scopePtr = astTypeFunctionEnvironmentScopes.find(function);
LUAU_ASSERT(scopePtr); LUAU_ASSERT(scopePtr);
@ -1817,7 +1796,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeFunctio
// Place this function as a child of the non-type function scope // Place this function as a child of the non-type function scope
scope->children.push_back(NotNull{sig.signatureScope.get()}); scope->children.push_back(NotNull{sig.signatureScope.get()});
if (FFlag::LuauNonReentrantGeneralization) if (FFlag::LuauNonReentrantGeneralization2)
interiorFreeTypes.emplace_back(); interiorFreeTypes.emplace_back();
else else
DEPRECATED_interiorTypes.push_back(std::vector<TypeId>{}); DEPRECATED_interiorTypes.push_back(std::vector<TypeId>{});
@ -1831,20 +1810,20 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeFunctio
GeneralizationConstraint{ GeneralizationConstraint{
generalizedTy, generalizedTy,
sig.signature, sig.signature,
FFlag::LuauTrackInteriorFreeTypesOnScope ? std::vector<TypeId>{} : std::move(DEPRECATED_interiorTypes.back()) std::vector<TypeId>{},
} }
); );
if (FFlag::LuauNonReentrantGeneralization) if (FFlag::LuauNonReentrantGeneralization2)
{ {
sig.signatureScope->interiorFreeTypes = std::move(interiorFreeTypes.back().types); sig.signatureScope->interiorFreeTypes = std::move(interiorFreeTypes.back().types);
sig.signatureScope->interiorFreeTypePacks = std::move(interiorFreeTypes.back().typePacks); sig.signatureScope->interiorFreeTypePacks = std::move(interiorFreeTypes.back().typePacks);
} }
else if (FFlag::LuauTrackInteriorFreeTypesOnScope) else
sig.signatureScope->interiorFreeTypes = std::move(DEPRECATED_interiorTypes.back()); sig.signatureScope->interiorFreeTypes = std::move(DEPRECATED_interiorTypes.back());
getMutable<BlockedType>(generalizedTy)->setOwner(gc); getMutable<BlockedType>(generalizedTy)->setOwner(gc);
if (FFlag::LuauNonReentrantGeneralization) if (FFlag::LuauNonReentrantGeneralization2)
interiorFreeTypes.pop_back(); interiorFreeTypes.pop_back();
else else
DEPRECATED_interiorTypes.pop_back(); DEPRECATED_interiorTypes.pop_back();
@ -1904,71 +1883,71 @@ static bool isMetamethod(const Name& name)
name == "__idiv"; 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 // 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()) if (bindingIt == scope->exportedTypeBindings.end())
return ControlFlow::None; return ControlFlow::None;
std::optional<TypeId> superTy = std::make_optional(builtinTypes->classType); std::optional<TypeId> superTy = std::make_optional(builtinTypes->externType);
if (declaredClass->superName) if (declaredExternType->superName)
{ {
Name superName = Name(declaredClass->superName->value); Name superName = Name(declaredExternType->superName->value);
std::optional<TypeFun> lookupType = scope->lookupType(superName); std::optional<TypeFun> lookupType = scope->lookupType(superName);
if (!lookupType) if (!lookupType)
{ {
reportError(declaredClass->location, UnknownSymbol{superName, UnknownSymbol::Type}); reportError(declaredExternType->location, UnknownSymbol{superName, UnknownSymbol::Type});
return ControlFlow::None; 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); LUAU_ASSERT(lookupType->typeParams.size() == 0 && lookupType->typePackParams.size() == 0);
superTy = follow(lookupType->type); superTy = follow(lookupType->type);
if (!get<ClassType>(follow(*superTy))) if (!get<ExternType>(follow(*superTy)))
{ {
reportError( reportError(
declaredClass->location, declaredExternType->location,
GenericError{format("Cannot use non-class type '%s' as a superclass of class '%s'", superName.c_str(), declaredClass->name.value)} GenericError{format("Cannot use non-class type '%s' as a superclass of class '%s'", superName.c_str(), declaredExternType->name.value)}
); );
return ControlFlow::None; 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)); TypeId externTy = arena->addType(ExternType(className, {}, superTy, std::nullopt, {}, {}, module->name, declaredExternType->location));
ClassType* ctv = getMutable<ClassType>(classTy); ExternType* etv = getMutable<ExternType>(externTy);
TypeId metaTy = arena->addType(TableType{TableState::Sealed, scope->level, scope.get()}); TypeId metaTy = arena->addType(TableType{TableState::Sealed, scope->level, scope.get()});
TableType* metatable = getMutable<TableType>(metaTy); TableType* metatable = getMutable<TableType>(metaTy);
ctv->metatable = metaTy; etv->metatable = metaTy;
TypeId classBindTy = bindingIt->second.type; TypeId classBindTy = bindingIt->second.type;
emplaceType<BoundType>(asMutable(classBindTy), classTy); emplaceType<BoundType>(asMutable(classBindTy), externTy);
if (declaredClass->indexer) if (declaredExternType->indexer)
{ {
RecursionCounter counter{&recursionCount}; RecursionCounter counter{&recursionCount};
if (recursionCount >= FInt::LuauCheckRecursionLimit) if (recursionCount >= FInt::LuauCheckRecursionLimit)
{ {
reportCodeTooComplex(declaredClass->indexer->location); reportCodeTooComplex(declaredExternType->indexer->location);
} }
else else
{ {
ctv->indexer = TableIndexer{ etv->indexer = TableIndexer{
resolveType(scope, declaredClass->indexer->indexType, /* inTypeArguments */ false), resolveType(scope, declaredExternType->indexer->indexType, /* inTypeArguments */ false),
resolveType(scope, declaredClass->indexer->resultType, /* 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); Name propName(prop.name.value);
TypeId propTy = resolveType(scope, prop.ty, /* inTypeArguments */ false); TypeId propTy = resolveType(scope, prop.ty, /* inTypeArguments */ false);
@ -1982,7 +1961,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareClas
if (FunctionType* ftv = getMutable<FunctionType>(propTy)) if (FunctionType* ftv = getMutable<FunctionType>(propTy))
{ {
ftv->argNames.insert(ftv->argNames.begin(), FunctionArgument{"self", {}}); ftv->argNames.insert(ftv->argNames.begin(), FunctionArgument{"self", {}});
ftv->argTypes = addTypePack({classTy}, ftv->argTypes); ftv->argTypes = addTypePack({externTy}, ftv->argTypes);
ftv->hasSelf = true; ftv->hasSelf = true;
@ -1997,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) if (props.count(propName) == 0)
{ {
@ -2028,7 +2007,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareClas
} }
else 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())});
} }
} }
} }
@ -2060,7 +2039,8 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareFunc
funScope = childScope(global, scope); funScope = childScope(global, scope);
TypePackId paramPack = resolveTypePack(funScope, global->params, /* inTypeArguments */ false); TypePackId paramPack = resolveTypePack(funScope, global->params, /* inTypeArguments */ false);
TypePackId retPack = 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; FunctionDefinition defn;
@ -2285,7 +2265,7 @@ InferencePack ConstraintGenerator::checkPack(const ScopePtr& scope, AstExprCall*
{ {
std::vector<TypeId> unpackedTypes; std::vector<TypeId> unpackedTypes;
if (args.size() > 0) if (args.size() > 0)
target = FFlag::LuauExtraFollows ? follow(args[0]) : args[0]; target = follow(args[0]);
else else
{ {
target = arena->addType(BlockedType{}); target = arena->addType(BlockedType{});
@ -2408,11 +2388,14 @@ InferencePack ConstraintGenerator::checkPack(const ScopePtr& scope, AstExprCall*
argEndCheckpoint, argEndCheckpoint,
this, this,
[checkConstraint, callConstraint](const ConstraintPtr& constraint) [checkConstraint, callConstraint](const ConstraintPtr& constraint)
{
if (!(FFlag::DebugLuauGreedyGeneralization && get<PrimitiveTypeConstraint>(*constraint)))
{ {
constraint->dependencies.emplace_back(checkConstraint); constraint->dependencies.emplace_back(checkConstraint);
callConstraint->dependencies.emplace_back(constraint.get()); callConstraint->dependencies.emplace_back(constraint.get());
} }
}
); );
return InferencePack{rets, {refinementArena.variadic(returnRefinements)}}; return InferencePack{rets, {refinementArena.variadic(returnRefinements)}};
@ -2506,7 +2489,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprConstantStrin
return Inference{arena->addType(SingletonType{StringSingleton{std::string{string->value.data, string->value.size}}})}; return Inference{arena->addType(SingletonType{StringSingleton{std::string{string->value.data, string->value.size}}})};
TypeId freeTy = nullptr; TypeId freeTy = nullptr;
if (FFlag::LuauNonReentrantGeneralization) if (FFlag::LuauNonReentrantGeneralization2)
{ {
freeTy = freshType(scope, Polarity::Positive); freeTy = freshType(scope, Polarity::Positive);
FreeType* ft = getMutable<FreeType>(freeTy); FreeType* ft = getMutable<FreeType>(freeTy);
@ -2516,8 +2499,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprConstantStrin
} }
else else
{ {
FreeType ft = FreeType ft = FreeType{scope.get(), builtinTypes->neverType, builtinTypes->unknownType};
FFlag::LuauFreeTypesMustHaveBounds ? FreeType{scope.get(), builtinTypes->neverType, builtinTypes->unknownType} : FreeType{scope.get()};
ft.lowerBound = arena->addType(SingletonType{StringSingleton{std::string{string->value.data, string->value.size}}}); ft.lowerBound = arena->addType(SingletonType{StringSingleton{std::string{string->value.data, string->value.size}}});
ft.upperBound = builtinTypes->stringType; ft.upperBound = builtinTypes->stringType;
freeTy = arena->addType(ft); freeTy = arena->addType(ft);
@ -2534,7 +2516,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprConstantBool*
return Inference{singletonType}; return Inference{singletonType};
TypeId freeTy = nullptr; TypeId freeTy = nullptr;
if (FFlag::LuauNonReentrantGeneralization) if (FFlag::LuauNonReentrantGeneralization2)
{ {
freeTy = freshType(scope, Polarity::Positive); freeTy = freshType(scope, Polarity::Positive);
FreeType* ft = getMutable<FreeType>(freeTy); FreeType* ft = getMutable<FreeType>(freeTy);
@ -2544,8 +2526,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprConstantBool*
} }
else else
{ {
FreeType ft = FreeType ft = FreeType{scope.get(), builtinTypes->neverType, builtinTypes->unknownType};
FFlag::LuauFreeTypesMustHaveBounds ? FreeType{scope.get(), builtinTypes->neverType, builtinTypes->unknownType} : FreeType{scope.get()};
ft.lowerBound = singletonType; ft.lowerBound = singletonType;
ft.upperBound = builtinTypes->booleanType; ft.upperBound = builtinTypes->booleanType;
freeTy = arena->addType(ft); freeTy = arena->addType(ft);
@ -2696,7 +2677,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprFunction* fun
Checkpoint startCheckpoint = checkpoint(this); Checkpoint startCheckpoint = checkpoint(this);
FunctionSignature sig = checkFunctionSignature(scope, func, expectedType); FunctionSignature sig = checkFunctionSignature(scope, func, expectedType);
if (FFlag::LuauNonReentrantGeneralization) if (FFlag::LuauNonReentrantGeneralization2)
interiorFreeTypes.emplace_back(); interiorFreeTypes.emplace_back();
else else
DEPRECATED_interiorTypes.push_back(std::vector<TypeId>{}); DEPRECATED_interiorTypes.push_back(std::vector<TypeId>{});
@ -2710,12 +2691,11 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprFunction* fun
GeneralizationConstraint{ GeneralizationConstraint{
generalizedTy, generalizedTy,
sig.signature, sig.signature,
(FFlag::LuauNonReentrantGeneralization || FFlag::LuauTrackInteriorFreeTypesOnScope) ? std::vector<TypeId>{} std::vector<TypeId>{},
: std::move(DEPRECATED_interiorTypes.back())
} }
); );
if (FFlag::LuauNonReentrantGeneralization) if (FFlag::LuauNonReentrantGeneralization2)
{ {
sig.signatureScope->interiorFreeTypes = std::move(interiorFreeTypes.back().types); sig.signatureScope->interiorFreeTypes = std::move(interiorFreeTypes.back().types);
sig.signatureScope->interiorFreeTypePacks = std::move(interiorFreeTypes.back().typePacks); sig.signatureScope->interiorFreeTypePacks = std::move(interiorFreeTypes.back().typePacks);
@ -2725,9 +2705,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprFunction* fun
} }
else 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); getMutable<BlockedType>(generalizedTy)->setOwner(gc);
DEPRECATED_interiorTypes.pop_back(); DEPRECATED_interiorTypes.pop_back();
} }
@ -3015,7 +2993,7 @@ std::tuple<TypeId, TypeId, RefinementId> ConstraintGenerator::checkBinary(
else if (typeguard->type == "userdata") else if (typeguard->type == "userdata")
{ {
// For now, we don't really care about being accurate with userdata if the typeguard was using typeof. // 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") else if (!typeguard->isTypeof && typeguard->type == "vector")
discriminantTy = builtinTypes->neverType; // TODO: figure out a way to deal with this quirky type discriminantTy = builtinTypes->neverType; // TODO: figure out a way to deal with this quirky type
@ -3025,8 +3003,8 @@ std::tuple<TypeId, TypeId, RefinementId> ConstraintGenerator::checkBinary(
{ {
TypeId ty = follow(typeFun->type); TypeId ty = follow(typeFun->type);
// We're only interested in the root class of any classes. // We're only interested in the root type of any extern type.
if (auto ctv = get<ClassType>(ty); ctv && (ctv->parent == builtinTypes->classType || hasTag(ty, kTypeofRootTag))) if (auto etv = get<ExternType>(ty); etv && (etv->parent == builtinTypes->externType || hasTag(ty, kTypeofRootTag)))
discriminantTy = ty; discriminantTy = ty;
} }
@ -3099,7 +3077,7 @@ void ConstraintGenerator::visitLValue(const ScopePtr& scope, AstExprLocal* local
if (ty) if (ty)
{ {
TypeIds* localDomain = localTypes.find(*ty); TypeIds* localDomain = localTypes.find(*ty);
if (localDomain) if (localDomain && !(FFlag::LuauDoNotAddUpvalueTypesToLocalType && local->upvalue))
localDomain->insert(rhsType); localDomain->insert(rhsType);
} }
else else
@ -3130,6 +3108,8 @@ void ConstraintGenerator::visitLValue(const ScopePtr& scope, AstExprLocal* local
if (annotatedTy) if (annotatedTy)
addConstraint(scope, local->location, SubtypeConstraint{rhsType, *annotatedTy}); addConstraint(scope, local->location, SubtypeConstraint{rhsType, *annotatedTy});
// This is vestigial.
if (!FFlag::LuauDoNotAddUpvalueTypesToLocalType)
if (TypeIds* localDomain = localTypes.find(*ty)) if (TypeIds* localDomain = localTypes.find(*ty))
localDomain->insert(rhsType); localDomain->insert(rhsType);
} }
@ -3142,12 +3122,9 @@ void ConstraintGenerator::visitLValue(const ScopePtr& scope, AstExprGlobal* glob
DefId def = dfg->getDef(global); DefId def = dfg->getDef(global);
rootScope->lvalueTypes[def] = rhsType; rootScope->lvalueTypes[def] = rhsType;
if (FFlag::LuauGlobalSelfAssignmentCycle)
{
// Ignore possible self-assignment, it doesn't create a new constraint // Ignore possible self-assignment, it doesn't create a new constraint
if (annotatedTy == follow(rhsType)) if (annotatedTy == follow(rhsType))
return; return;
}
// Sketchy: We're specifically looking for BlockedTypes that were // Sketchy: We're specifically looking for BlockedTypes that were
// initially created by ConstraintGenerator::prepopulateGlobalScope. // initially created by ConstraintGenerator::prepopulateGlobalScope.
@ -3210,7 +3187,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTable* expr,
ttv->definitionLocation = expr->location; ttv->definitionLocation = expr->location;
ttv->scope = scope.get(); ttv->scope = scope.get();
if (FFlag::LuauNonReentrantGeneralization) if (FFlag::LuauNonReentrantGeneralization2)
interiorFreeTypes.back().types.push_back(ty); interiorFreeTypes.back().types.push_back(ty);
else else
DEPRECATED_interiorTypes.back().push_back(ty); DEPRECATED_interiorTypes.back().push_back(ty);
@ -3373,7 +3350,7 @@ ConstraintGenerator::FunctionSignature ConstraintGenerator::checkFunctionSignatu
if (fn->self) if (fn->self)
{ {
TypeId selfType = freshType(signatureScope); TypeId selfType = freshType(signatureScope, Polarity::Negative);
argTypes.push_back(selfType); argTypes.push_back(selfType);
argNames.emplace_back(FunctionArgument{fn->self->name.value, fn->self->location}); argNames.emplace_back(FunctionArgument{fn->self->name.value, fn->self->location});
signatureScope->bindings[fn->self] = Binding{selfType, fn->self->location}; signatureScope->bindings[fn->self] = Binding{selfType, fn->self->location};
@ -3438,12 +3415,36 @@ ConstraintGenerator::FunctionSignature ConstraintGenerator::checkFunctionSignatu
LUAU_ASSERT(nullptr != varargPack); 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. // If there is both an annotation and an expected type, the annotation wins.
// Type checking will sort out any discrepancies later. // Type checking will sort out any discrepancies later.
if (fn->returnAnnotation) if (FFlag::LuauStoreReturnTypesAsPackOnAst && fn->returnAnnotation)
{ {
TypePackId annotatedRetType = 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 // We bind the annotated type directly here so that, when we need to
// generate constraints for return types, we have a guarantee that we // generate constraints for return types, we have a guarantee that we
// know the annotated return type already, if one was provided. // know the annotated return type already, if one was provided.
@ -3694,8 +3695,16 @@ TypeId ConstraintGenerator::resolveFunctionType(
AstTypePackExplicit tempArgTypes{Location{}, fn->argTypes}; AstTypePackExplicit tempArgTypes{Location{}, fn->argTypes};
TypePackId argTypes = resolveTypePack_(signatureScope, &tempArgTypes, inTypeArguments, replaceErrorWithFresh); TypePackId argTypes = resolveTypePack_(signatureScope, &tempArgTypes, inTypeArguments, replaceErrorWithFresh);
AstTypePackExplicit tempRetTypes{Location{}, fn->returnTypes}; TypePackId returnTypes;
TypePackId returnTypes = resolveTypePack_(signatureScope, &tempRetTypes, inTypeArguments, replaceErrorWithFresh); if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
returnTypes = resolveTypePack_(signatureScope, fn->returnTypes, inTypeArguments, replaceErrorWithFresh);
}
else
{
AstTypePackExplicit tempRetTypes{Location{}, fn->returnTypes_DEPRECATED};
returnTypes = resolveTypePack_(signatureScope, &tempRetTypes, inTypeArguments, replaceErrorWithFresh);
}
// TODO: FunctionType needs a pointer to the scope so that we know // TODO: FunctionType needs a pointer to the scope so that we know
// how to quantify/instantiate it. // how to quantify/instantiate it.
@ -3779,13 +3788,9 @@ TypeId ConstraintGenerator::resolveType_(const ScopePtr& scope, AstType* ty, boo
} }
} }
else else
{
if (FFlag::LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
{ {
if (unionAnnotation->types.size == 1) if (unionAnnotation->types.size == 1)
return resolveType_(scope, unionAnnotation->types.data[0], inTypeArguments); return resolveType_(scope, unionAnnotation->types.data[0], inTypeArguments);
}
std::vector<TypeId> parts; std::vector<TypeId> parts;
for (AstType* part : unionAnnotation->types) for (AstType* part : unionAnnotation->types)
{ {
@ -3813,13 +3818,9 @@ TypeId ConstraintGenerator::resolveType_(const ScopePtr& scope, AstType* ty, boo
} }
} }
else else
{
if (FFlag::LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
{ {
if (intersectionAnnotation->types.size == 1) if (intersectionAnnotation->types.size == 1)
return resolveType_(scope, intersectionAnnotation->types.data[0], inTypeArguments); return resolveType_(scope, intersectionAnnotation->types.data[0], inTypeArguments);
}
std::vector<TypeId> parts; std::vector<TypeId> parts;
for (AstType* part : intersectionAnnotation->types) for (AstType* part : intersectionAnnotation->types)
{ {
@ -4043,14 +4044,14 @@ TypeId ConstraintGenerator::makeIntersect(const ScopePtr& scope, Location locati
return resultType; return resultType;
} }
struct FragmentTypeCheckGlobalPrepopulator : AstVisitor struct FragmentTypeCheckGlobalPrepopulator_DEPRECATED : AstVisitor
{ {
const NotNull<Scope> globalScope; const NotNull<Scope> globalScope;
const NotNull<Scope> currentScope; const NotNull<Scope> currentScope;
const NotNull<const DataFlowGraph> dfg; const NotNull<const DataFlowGraph> dfg;
const NotNull<TypeArena> arena; const NotNull<TypeArena> arena;
FragmentTypeCheckGlobalPrepopulator( FragmentTypeCheckGlobalPrepopulator_DEPRECATED(
NotNull<Scope> globalScope, NotNull<Scope> globalScope,
NotNull<Scope> currentScope, NotNull<Scope> currentScope,
NotNull<const DataFlowGraph> dfg, NotNull<const DataFlowGraph> dfg,
@ -4167,12 +4168,16 @@ struct GlobalPrepopulator : AstVisitor
void ConstraintGenerator::prepopulateGlobalScopeForFragmentTypecheck(const ScopePtr& globalScope, const ScopePtr& resumeScope, AstStatBlock* program) void ConstraintGenerator::prepopulateGlobalScopeForFragmentTypecheck(const ScopePtr& globalScope, const ScopePtr& resumeScope, AstStatBlock* program)
{ {
FragmentTypeCheckGlobalPrepopulator gp{NotNull{globalScope.get()}, NotNull{resumeScope.get()}, dfg, arena}; if (!FFlag::LuauGlobalVariableModuleIsolation)
{
FragmentTypeCheckGlobalPrepopulator_DEPRECATED gp{NotNull{globalScope.get()}, NotNull{resumeScope.get()}, dfg, arena};
if (prepareModuleScope) if (prepareModuleScope)
prepareModuleScope(module->name, resumeScope); prepareModuleScope(module->name, resumeScope);
program->visit(&gp); program->visit(&gp);
}
if (FFlag::LuauUserTypeFunTypecheck) if (FFlag::LuauUserTypeFunTypecheck)
{ {
@ -4345,15 +4350,4 @@ TypeId ConstraintGenerator::simplifyUnion(const ScopePtr& scope, Location locati
return ::Luau::simplifyUnion(builtinTypes, arena, left, right).result; 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 } // namespace Luau

View file

@ -11,7 +11,6 @@
#include "Luau/Location.h" #include "Luau/Location.h"
#include "Luau/ModuleResolver.h" #include "Luau/ModuleResolver.h"
#include "Luau/OverloadResolution.h" #include "Luau/OverloadResolution.h"
#include "Luau/Quantify.h"
#include "Luau/RecursionCounter.h" #include "Luau/RecursionCounter.h"
#include "Luau/Simplify.h" #include "Luau/Simplify.h"
#include "Luau/TableLiteralInference.h" #include "Luau/TableLiteralInference.h"
@ -33,20 +32,20 @@ LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverIncludeDependencies)
LUAU_FASTFLAGVARIABLE(DebugLuauLogBindings) LUAU_FASTFLAGVARIABLE(DebugLuauLogBindings)
LUAU_FASTINTVARIABLE(LuauSolverRecursionLimit, 500) LUAU_FASTINTVARIABLE(LuauSolverRecursionLimit, 500)
LUAU_FASTFLAGVARIABLE(DebugLuauEqSatSimplification) LUAU_FASTFLAGVARIABLE(DebugLuauEqSatSimplification)
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope)
LUAU_FASTFLAGVARIABLE(LuauTrackInteriorFreeTablesOnScope)
LUAU_FASTFLAGVARIABLE(LuauHasPropProperBlock) LUAU_FASTFLAGVARIABLE(LuauHasPropProperBlock)
LUAU_FASTFLAGVARIABLE(DebugLuauGreedyGeneralization) LUAU_FASTFLAGVARIABLE(DebugLuauGreedyGeneralization)
LUAU_FASTFLAG(LuauSearchForRefineableType)
LUAU_FASTFLAG(LuauDeprecatedAttribute) LUAU_FASTFLAG(LuauDeprecatedAttribute)
LUAU_FASTFLAG(LuauNonReentrantGeneralization) LUAU_FASTFLAG(LuauNonReentrantGeneralization2)
LUAU_FASTFLAG(LuauBidirectionalInferenceCollectIndexerTypes) LUAU_FASTFLAG(LuauBidirectionalInferenceCollectIndexerTypes)
LUAU_FASTFLAG(LuauNewTypeFunReductionChecks2) LUAU_FASTFLAG(LuauNewTypeFunReductionChecks2)
LUAU_FASTFLAGVARIABLE(LuauTrackInferredFunctionTypeFromCall) LUAU_FASTFLAGVARIABLE(LuauTrackInferredFunctionTypeFromCall)
LUAU_FASTFLAGVARIABLE(LuauAddCallConstraintForIterableFunctions)
namespace Luau namespace Luau
{ {
static void dump(ConstraintSolver* cs, ToStringOptions& opts);
size_t HashBlockedConstraintId::operator()(const BlockedConstraintId& bci) const size_t HashBlockedConstraintId::operator()(const BlockedConstraintId& bci) const
{ {
size_t result = 0; size_t result = 0;
@ -276,26 +275,6 @@ size_t HashInstantiationSignature::operator()(const InstantiationSignature& sign
return hash; 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 struct InstantiationQueuer : TypeOnceVisitor
{ {
ConstraintSolver* solver; ConstraintSolver* solver;
@ -321,12 +300,44 @@ struct InstantiationQueuer : TypeOnceVisitor
return true; return true;
} }
bool visit(TypeId ty, const ClassType& ctv) override bool visit(TypeId ty, const ExternType& etv) override
{ {
return false; 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( ConstraintSolver::ConstraintSolver(
NotNull<Normalizer> normalizer, NotNull<Normalizer> normalizer,
NotNull<Simplifier> simplifier, NotNull<Simplifier> simplifier,
@ -346,6 +357,7 @@ ConstraintSolver::ConstraintSolver(
, normalizer(normalizer) , normalizer(normalizer)
, simplifier(simplifier) , simplifier(simplifier)
, typeFunctionRuntime(typeFunctionRuntime) , typeFunctionRuntime(typeFunctionRuntime)
, constraintSet{rootScope}
, constraints(std::move(constraints)) , constraints(std::move(constraints))
, scopeToFunction(scopeToFunction) , scopeToFunction(scopeToFunction)
, rootScope(rootScope) , rootScope(rootScope)
@ -355,33 +367,9 @@ ConstraintSolver::ConstraintSolver(
, requireCycles(std::move(requireCycles)) , requireCycles(std::move(requireCycles))
, logger(logger) , logger(logger)
, limits(std::move(limits)) , limits(std::move(limits))
, opts{/*exhaustive*/ true}
{ {
opts.exhaustive = true; 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, DenseHashSet<const Constraint*>{nullptr});
it->second.insert(c.get());
}
}
maybeMutatedFreeTypes.emplace(c, maybeMutatedTypesPerConstraint);
for (NotNull<const Constraint> dep : c->dependencies)
{
block(dep, c);
}
}
} }
void ConstraintSolver::randomize(unsigned seed) void ConstraintSolver::randomize(unsigned seed)
@ -426,6 +414,18 @@ void ConstraintSolver::run()
logger->captureInitialSolverState(rootScope, unsolvedConstraints); 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) auto runSolverPass = [&](bool force)
{ {
bool progress = false; 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; 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) void ConstraintSolver::generalizeOneType(TypeId ty)
{ {
ty = follow(ty); ty = follow(ty);
@ -673,73 +700,20 @@ void ConstraintSolver::generalizeOneType(TypeId ty)
if (!freeTy) if (!freeTy)
return; return;
NotNull<Scope> tyScope{freeTy->scope}; TypeId* functionType = scopeToFunction->find(freeTy->scope);
if (!functionType)
return;
// TODO: If freeTy occurs within the enclosing function's type, we need to std::optional<TypeId> resultTy = generalize(arena, builtinTypes, NotNull{freeTy->scope}, generalizedTypes, *functionType, ty);
// check to see whether this type should instead be generic.
TypeId newBound = follow(freeTy->upperBound); if (FFlag::DebugLuauLogSolver)
TypeId* functionTyPtr = nullptr;
while (true)
{ {
functionTyPtr = scopeToFunction->find(tyScope); printf(
if (functionTyPtr || !tyScope->parent) "Eagerly generalized %s (now %s)\n\tin function %s\n",
break; saveme.c_str(),
else if (tyScope->parent) toString(ty, opts).c_str(),
tyScope = NotNull{tyScope->parent.get()}; toString(resultTy.value_or(*functionType), opts).c_str()
else );
break;
}
if (ty == newBound)
ty = builtinTypes->unknownType;
if (!functionTyPtr)
{
asMutable(ty)->reassign(Type{BoundType{follow(freeTy->upperBound)}});
}
else
{
const TypeId functionTy = follow(*functionTyPtr);
FunctionType* const function = getMutable<FunctionType>(functionTy);
LUAU_ASSERT(function);
TypeSearcher ts{ty};
ts.traverse(functionTy);
const TypeId upperBound = follow(freeTy->upperBound);
const TypeId lowerBound = follow(freeTy->lowerBound);
switch (ts.result)
{
case Polarity::None:
asMutable(ty)->reassign(Type{BoundType{upperBound}});
break;
case Polarity::Negative:
case Polarity::Mixed:
if (get<UnknownType>(upperBound) && ts.count > 1)
{
asMutable(ty)->reassign(Type{GenericType{tyScope}});
function->generics.emplace_back(ty);
}
else
asMutable(ty)->reassign(Type{BoundType{upperBound}});
break;
case Polarity::Positive:
if (get<UnknownType>(lowerBound) && ts.count > 1)
{
asMutable(ty)->reassign(Type{GenericType{tyScope}});
function->generics.emplace_back(ty);
}
else
asMutable(ty)->reassign(Type{BoundType{lowerBound}});
break;
default:
LUAU_ASSERT(!"Unreachable");
}
} }
} }
@ -755,7 +729,7 @@ void ConstraintSolver::bind(NotNull<const Constraint> constraint, TypeId ty, Typ
constraint, ty, constraint->scope, builtinTypes->neverType, builtinTypes->unknownType, Polarity::Mixed constraint, ty, constraint->scope, builtinTypes->neverType, builtinTypes->unknownType, Polarity::Mixed
); // FIXME? Is this the right polarity? ); // FIXME? Is this the right polarity?
if (FFlag::LuauNonReentrantGeneralization) if (FFlag::LuauNonReentrantGeneralization2)
trackInteriorFreeType(constraint->scope, ty); trackInteriorFreeType(constraint->scope, ty);
return; return;
@ -890,6 +864,7 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNull<co
if (generalizedTy) if (generalizedTy)
{ {
pruneUnnecessaryGenerics(arena, builtinTypes, constraint->scope, generalizedTypes, *generalizedTy);
if (get<BlockedType>(generalizedType)) if (get<BlockedType>(generalizedType))
bind(constraint, generalizedType, *generalizedTy); bind(constraint, generalizedType, *generalizedTy);
else else
@ -910,15 +885,13 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNull<co
bind(constraint, c.generalizedType, builtinTypes->errorRecoveryType()); bind(constraint, c.generalizedType, builtinTypes->errorRecoveryType());
} }
if (FFlag::LuauTrackInteriorFreeTypesOnScope)
{
// We check if this member is initialized and then access it, but // We check if this member is initialized and then access it, but
// clang-tidy doesn't understand this is safe. // clang-tidy doesn't understand this is safe.
if (constraint->scope->interiorFreeTypes) 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::LuauNonReentrantGeneralization) if (FFlag::LuauNonReentrantGeneralization2)
{ {
ty = follow(ty); ty = follow(ty);
if (auto freeTy = get<FreeType>(ty)) if (auto freeTy = get<FreeType>(ty))
@ -928,7 +901,9 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNull<co
params.useCount = 1; params.useCount = 1;
params.polarity = freeTy->polarity; params.polarity = freeTy->polarity;
generalizeType(arena, builtinTypes, constraint->scope, ty, params); 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)) else if (get<TableType>(ty))
sealTable(constraint->scope, ty); sealTable(constraint->scope, ty);
@ -938,7 +913,7 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNull<co
} }
} }
if (FFlag::LuauNonReentrantGeneralization) if (FFlag::LuauNonReentrantGeneralization2)
{ {
if (constraint->scope->interiorFreeTypePacks) if (constraint->scope->interiorFreeTypePacks)
{ {
@ -957,13 +932,6 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNull<co
} }
} }
} }
}
else
{
for (TypeId ty : c.interiorTypes)
generalize(NotNull{arena}, builtinTypes, constraint->scope, generalizedTypes, ty);
}
return true; return true;
} }
@ -1039,15 +1007,11 @@ bool ConstraintSolver::tryDispatch(const IterableConstraint& c, NotNull<const Co
{ {
TypeId keyTy = freshType(arena, builtinTypes, constraint->scope, Polarity::Mixed); TypeId keyTy = freshType(arena, builtinTypes, constraint->scope, Polarity::Mixed);
TypeId valueTy = 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, keyTy);
trackInteriorFreeType(constraint->scope, valueTy); trackInteriorFreeType(constraint->scope, valueTy);
}
TypeId tableTy = TypeId tableTy =
arena->addType(TableType{TableType::Props{}, TableIndexer{keyTy, valueTy}, TypeLevel{}, constraint->scope, TableState::Free}); 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); unify(constraint, nextTy, tableTy);
@ -1372,8 +1336,6 @@ void ConstraintSolver::fillInDiscriminantTypes(NotNull<const Constraint> constra
if (!ty) if (!ty)
continue; continue;
if (FFlag::LuauSearchForRefineableType)
{
if (isBlocked(*ty)) if (isBlocked(*ty))
// We bind any unused discriminants to the `*no-refine*` type indicating that it can be safely ignored. // We bind any unused discriminants to the `*no-refine*` type indicating that it can be safely ignored.
emplaceType<BoundType>(asMutable(follow(*ty)), builtinTypes->noRefineType); emplaceType<BoundType>(asMutable(follow(*ty)), builtinTypes->noRefineType);
@ -1381,20 +1343,7 @@ void ConstraintSolver::fillInDiscriminantTypes(NotNull<const Constraint> constra
// We also need to unconditionally unblock these types, otherwise // We also need to unconditionally unblock these types, otherwise
// you end up with funky looking "Blocked on *no-refine*." // you end up with funky looking "Blocked on *no-refine*."
unblock(*ty, constraint->location); 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;
}
// We bind any unused discriminants to the `*no-refine*` type indicating that it can be safely ignored.
emplaceType<BoundType>(asMutable(follow(*ty)), builtinTypes->noRefineType);
}
} }
} }
@ -1507,9 +1456,9 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
if (ftv) 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}); ftv->magic->refine(MagicRefinementContext{constraint->scope, c.callSite, c.discriminantTypes});
} }
} }
@ -1544,7 +1493,7 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
const bool occursCheckPassed = u2.unify(overloadToUse, inferredTy); const bool occursCheckPassed = u2.unify(overloadToUse, inferredTy);
if (FFlag::LuauNonReentrantGeneralization) if (FFlag::LuauNonReentrantGeneralization2)
{ {
for (TypeId freeTy : u2.newFreshTypes) for (TypeId freeTy : u2.newFreshTypes)
trackInteriorFreeType(constraint->scope, freeTy); trackInteriorFreeType(constraint->scope, freeTy);
@ -1584,7 +1533,7 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
// This can potentially contain free types if the return type of // This can potentially contain free types if the return type of
// `inferredTy` is never unified elsewhere. // `inferredTy` is never unified elsewhere.
if (FFlag::LuauTrackInteriorFreeTypesOnScope && FFlag::LuauTrackInferredFunctionTypeFromCall) if (FFlag::LuauTrackInferredFunctionTypeFromCall)
trackInteriorFreeType(constraint->scope, inferredTy); trackInteriorFreeType(constraint->scope, inferredTy);
unblock(c.result, constraint->location); unblock(c.result, constraint->location);
@ -1944,7 +1893,7 @@ bool ConstraintSolver::tryDispatchHasIndexer(
FreeType freeResult{tt->scope, builtinTypes->neverType, builtinTypes->unknownType, Polarity::Mixed}; FreeType freeResult{tt->scope, builtinTypes->neverType, builtinTypes->unknownType, Polarity::Mixed};
emplace<FreeType>(constraint, resultType, freeResult); emplace<FreeType>(constraint, resultType, freeResult);
if (FFlag::LuauNonReentrantGeneralization) if (FFlag::LuauNonReentrantGeneralization2)
trackInteriorFreeType(constraint->scope, resultType); trackInteriorFreeType(constraint->scope, resultType);
tt->indexer = TableIndexer{indexType, resultType}; tt->indexer = TableIndexer{indexType, resultType};
@ -1953,7 +1902,7 @@ bool ConstraintSolver::tryDispatchHasIndexer(
} }
else if (auto mt = get<MetatableType>(subjectType)) else if (auto mt = get<MetatableType>(subjectType))
return tryDispatchHasIndexer(recursionDepth, constraint, mt->table, indexType, resultType, seen); 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) if (auto indexer = ct->indexer)
{ {
@ -2115,9 +2064,9 @@ bool ConstraintSolver::tryDispatch(const AssignPropConstraint& c, NotNull<const
// Important: In every codepath through this function, the type `c.propType` // Important: In every codepath through this function, the type `c.propType`
// must be bound to something, even if it's just the errorType. // 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()) if (!prop || !prop->writeTy.has_value())
{ {
bind(constraint, c.propType, builtinTypes->anyType); bind(constraint, c.propType, builtinTypes->anyType);
@ -2132,13 +2081,26 @@ bool ConstraintSolver::tryDispatch(const AssignPropConstraint& c, NotNull<const
if (auto lhsFree = getMutable<FreeType>(lhsType)) if (auto lhsFree = getMutable<FreeType>(lhsType))
{ {
auto lhsFreeUpperBound = follow(lhsFree->upperBound); auto lhsFreeUpperBound = follow(lhsFree->upperBound);
if (get<TableType>(lhsFreeUpperBound) || get<MetatableType>(lhsFreeUpperBound))
lhsType = lhsFreeUpperBound; 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 else
{ {
TypeId newUpperBound = arena->addType(TableType{TableState::Free, TypeLevel{}, constraint->scope}); TypeId newUpperBound = arena->addType(TableType{TableState::Free, TypeLevel{}, constraint->scope});
if (FFlag::LuauTrackInteriorFreeTypesOnScope && FFlag::LuauTrackInteriorFreeTablesOnScope)
trackInteriorFreeType(constraint->scope, newUpperBound); trackInteriorFreeType(constraint->scope, newUpperBound);
TableType* upperTable = getMutable<TableType>(newUpperBound); TableType* upperTable = getMutable<TableType>(newUpperBound);
@ -2153,6 +2115,29 @@ bool ConstraintSolver::tryDispatch(const AssignPropConstraint& c, NotNull<const
return true; 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;
}
}
}
// Handle the case that lhsType is a table that already has the property or // Handle the case that lhsType is a table that already has the property or
// a matching indexer. This also handles unions and intersections. // a matching indexer. This also handles unions and intersections.
@ -2302,20 +2287,20 @@ bool ConstraintSolver::tryDispatch(const AssignIndexConstraint& c, NotNull<const
return *res; return *res;
} }
if (auto lhsClass = get<ClassType>(lhsType)) if (auto lhsExternType = get<ExternType>(lhsType))
{ {
while (true) while (true)
{ {
if (lhsClass->indexer) if (lhsExternType->indexer)
{ {
unify(constraint, indexType, lhsClass->indexer->indexType); unify(constraint, indexType, lhsExternType->indexer->indexType);
unify(constraint, rhsType, lhsClass->indexer->indexResultType); unify(constraint, rhsType, lhsExternType->indexer->indexResultType);
bind(constraint, c.propType, arena->addType(UnionType{{lhsClass->indexer->indexResultType, builtinTypes->nilType}})); bind(constraint, c.propType, arena->addType(UnionType{{lhsExternType->indexer->indexResultType, builtinTypes->nilType}}));
return true; return true;
} }
if (lhsClass->parent) if (lhsExternType->parent)
lhsClass = get<ClassType>(lhsClass->parent); lhsExternType = get<ExternType>(lhsExternType->parent);
else else
break; break;
} }
@ -2342,7 +2327,7 @@ bool ConstraintSolver::tryDispatch(const AssignIndexConstraint& c, NotNull<const
parts.insert(rhsType); parts.insert(rhsType);
} }
} }
else if (auto cls = get<ClassType>(follow(t))) else if (auto cls = get<ExternType>(follow(t)))
{ {
while (true) while (true)
{ {
@ -2354,7 +2339,7 @@ bool ConstraintSolver::tryDispatch(const AssignIndexConstraint& c, NotNull<const
} }
if (cls->parent) if (cls->parent)
cls = get<ClassType>(cls->parent); cls = get<ExternType>(cls->parent);
else else
break; break;
} }
@ -2405,7 +2390,6 @@ bool ConstraintSolver::tryDispatch(const UnpackConstraint& c, NotNull<const Cons
// constitute any meaningful constraint, so we replace it // constitute any meaningful constraint, so we replace it
// with a free type. // with a free type.
TypeId f = freshType(arena, builtinTypes, constraint->scope, Polarity::Positive); // FIXME? Is this the right polarity? 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); shiftReferences(resultTy, f);
emplaceType<BoundType>(asMutable(resultTy), f); emplaceType<BoundType>(asMutable(resultTy), f);
@ -2549,11 +2533,8 @@ bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const Iterabl
{ {
TypeId keyTy = freshType(arena, builtinTypes, constraint->scope, Polarity::Mixed); TypeId keyTy = freshType(arena, builtinTypes, constraint->scope, Polarity::Mixed);
TypeId valueTy = 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, keyTy);
trackInteriorFreeType(constraint->scope, valueTy); trackInteriorFreeType(constraint->scope, valueTy);
}
TypeId tableTy = arena->addType(TableType{TableState::Sealed, {}, constraint->scope}); TypeId tableTy = arena->addType(TableType{TableState::Sealed, {}, constraint->scope});
getMutable<TableType>(tableTy)->indexer = TableIndexer{keyTy, valueTy}; getMutable<TableType>(tableTy)->indexer = TableIndexer{keyTy, valueTy};
@ -2703,12 +2684,31 @@ bool ConstraintSolver::tryDispatchIterableFunction(TypeId nextTy, TypeId tableTy
const FunctionType* nextFn = get<FunctionType>(nextTy); const FunctionType* nextFn = get<FunctionType>(nextTy);
// If this does not hold, we should've never called `tryDispatchIterableFunction` in the first place. // If this does not hold, we should've never called `tryDispatchIterableFunction` in the first place.
LUAU_ASSERT(nextFn); LUAU_ASSERT(nextFn);
const TypePackId nextRetPack = nextFn->retTypes;
// the type of the `nextAstFragment` is the `nextTy`. // the type of the `nextAstFragment` is the `nextTy`.
(*c.astForInNextTypes)[c.nextAstFragment] = nextTy; (*c.astForInNextTypes)[c.nextAstFragment] = nextTy;
auto it = begin(nextRetPack); if (FFlag::LuauAddCallConstraintForIterableFunctions)
{
// 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; std::vector<TypeId> modifiedNextRetHead;
// The first value is never nil in the context of the loop, even if it's nil // The first value is never nil in the context of the loop, even if it's nil
@ -2730,6 +2730,7 @@ bool ConstraintSolver::tryDispatchIterableFunction(TypeId nextTy, TypeId tableTy
auto unpackConstraint = unpackAndAssign(c.variables, modifiedNextRetPack, constraint); auto unpackConstraint = unpackAndAssign(c.variables, modifiedNextRetPack, constraint);
inheritBlocks(constraint, unpackConstraint); inheritBlocks(constraint, unpackConstraint);
}
return true; return true;
} }
@ -2810,7 +2811,6 @@ TablePropLookupResult ConstraintSolver::lookupTableProp(
if (ttv->state == TableState::Free) if (ttv->state == TableState::Free)
{ {
TypeId result = freshType(arena, builtinTypes, ttv->scope, Polarity::Mixed); TypeId result = freshType(arena, builtinTypes, ttv->scope, Polarity::Mixed);
if (FFlag::LuauTrackInteriorFreeTypesOnScope)
trackInteriorFreeType(ttv->scope, result); trackInteriorFreeType(ttv->scope, result);
switch (context) switch (context)
{ {
@ -2885,9 +2885,9 @@ TablePropLookupResult ConstraintSolver::lookupTableProp(
else if (get<MetatableType>(mtt)) else if (get<MetatableType>(mtt))
return lookupTableProp(constraint, mtt, propName, context, inConditional, suppressSimplification, seen); 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}; return {{}, context == ValueContext::RValue ? p->readTy : p->writeTy};
if (ct->indexer) if (ct->indexer)
{ {
@ -2909,23 +2909,33 @@ TablePropLookupResult ConstraintSolver::lookupTableProp(
{ {
const TypeId upperBound = follow(ft->upperBound); const TypeId upperBound = follow(ft->upperBound);
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)) if (get<TableType>(upperBound) || get<PrimitiveType>(upperBound))
return lookupTableProp(constraint, upperBound, propName, context, inConditional, suppressSimplification, seen); 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}; NotNull<Scope> scope{ft->scope};
const TypeId newUpperBound = arena->addType(TableType{TableState::Free, TypeLevel{}, 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); TableType* tt = getMutable<TableType>(newUpperBound);
LUAU_ASSERT(tt); LUAU_ASSERT(tt);
TypeId propType = freshType(arena, builtinTypes, scope, Polarity::Mixed); TypeId propType = freshType(arena, builtinTypes, scope, Polarity::Mixed);
if (FFlag::LuauTrackInteriorFreeTypesOnScope)
trackInteriorFreeType(scope, propType); trackInteriorFreeType(scope, propType);
switch (context) switch (context)
@ -3145,7 +3155,7 @@ struct Blocker : TypeOnceVisitor
return false; return false;
} }
bool visit(TypeId ty, const ClassType&) override bool visit(TypeId ty, const ExternType&) override
{ {
return false; return false;
} }
@ -3521,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, TypeId subTy, TypeId superTy);
template bool ConstraintSolver::unify(NotNull<const Constraint> constraint, TypePackId subTy, TypePackId 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 } // namespace Luau

View file

@ -16,6 +16,9 @@ LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAGVARIABLE(LuauPreprocessTypestatedArgument) LUAU_FASTFLAGVARIABLE(LuauPreprocessTypestatedArgument)
LUAU_FASTFLAGVARIABLE(LuauDfgScopeStackTrueReset) LUAU_FASTFLAGVARIABLE(LuauDfgScopeStackTrueReset)
LUAU_FASTFLAGVARIABLE(LuauDfgScopeStackNotNull) LUAU_FASTFLAGVARIABLE(LuauDfgScopeStackNotNull)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
LUAU_FASTFLAGVARIABLE(LuauDoNotAddUpvalueTypesToLocalType)
LUAU_FASTFLAGVARIABLE(LuauDfgIfBlocksShouldRespectControlFlow)
namespace Luau namespace Luau
{ {
@ -470,7 +473,7 @@ ControlFlow DataFlowGraphBuilder::visit(AstStat* s)
return visit(d); return visit(d);
else if (auto d = s->as<AstStatDeclareFunction>()) else if (auto d = s->as<AstStatDeclareFunction>())
return visit(d); return visit(d);
else if (auto d = s->as<AstStatDeclareClass>()) else if (auto d = s->as<AstStatDeclareExternType>())
return visit(d); return visit(d);
else if (auto error = s->as<AstStatError>()) else if (auto error = s->as<AstStatError>())
return visit(error); return visit(error);
@ -499,12 +502,26 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatIf* i)
} }
DfgScope* scope = FFlag::LuauDfgScopeStackNotNull ? currentScope() : currentScope_DEPRECATED(); DfgScope* scope = FFlag::LuauDfgScopeStackNotNull ? currentScope() : currentScope_DEPRECATED();
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) if (thencf != ControlFlow::None && elsecf == ControlFlow::None)
join(scope, scope, elseScope); join(scope, scope, elseScope);
else if (thencf == ControlFlow::None && elsecf != ControlFlow::None) else if (thencf == ControlFlow::None && elsecf != ControlFlow::None)
join(scope, thenScope, scope); join(scope, thenScope, scope);
else if ((thencf | elsecf) == ControlFlow::None) else if ((thencf | elsecf) == ControlFlow::None)
join(scope, thenScope, elseScope); join(scope, thenScope, elseScope);
}
if (thencf == elsecf) if (thencf == elsecf)
return thencf; return thencf;
@ -808,12 +825,15 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatDeclareFunction* d)
visitGenerics(d->generics); visitGenerics(d->generics);
visitGenericPacks(d->genericPacks); visitGenericPacks(d->genericPacks);
visitTypeList(d->params); visitTypeList(d->params);
visitTypeList(d->retTypes); if (FFlag::LuauStoreReturnTypesAsPackOnAst)
visitTypePack(d->retTypes);
else
visitTypeList(d->retTypes_DEPRECATED);
return ControlFlow::None; return ControlFlow::None;
} }
ControlFlow DataFlowGraphBuilder::visit(AstStatDeclareClass* d) ControlFlow DataFlowGraphBuilder::visit(AstStatDeclareExternType* d)
{ {
// This declaration does not "introduce" any bindings in value namespace, // This declaration does not "introduce" any bindings in value namespace,
// so there's no symbolic value to begin with. We'll traverse the properties // 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(); DfgScope* unreachable = makeChildScope();
PushScope ps{scopeStack, unreachable}; PushScope ps{scopeStack, unreachable};
for (AstDeclaredClassProp prop : d->props) for (AstDeclaredExternTypeProperty prop : d->props)
visitType(prop.ty); visitType(prop.ty);
return ControlFlow::None; return ControlFlow::None;
@ -1032,8 +1052,16 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprFunction* f)
if (f->varargAnnotation) if (f->varargAnnotation)
visitTypePack(f->varargAnnotation); visitTypePack(f->varargAnnotation);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
if (f->returnAnnotation) if (f->returnAnnotation)
visitTypeList(*f->returnAnnotation); visitTypePack(f->returnAnnotation);
}
else
{
if (f->returnAnnotation_DEPRECATED)
visitTypeList(*f->returnAnnotation_DEPRECATED);
}
// TODO: function body can be re-entrant, as in mutations that occurs at the end of the function can also be // TODO: function body can be re-entrant, as in mutations that occurs at the end of the function can also be
// visible to the beginning of the function, so statically speaking, the body of the function has an exit point // visible to the beginning of the function, so statically speaking, the body of the function has an exit point
@ -1151,7 +1179,7 @@ DefId DataFlowGraphBuilder::visitLValue(AstExprLocal* l, DefId incomingDef)
DfgScope* scope = FFlag::LuauDfgScopeStackNotNull ? currentScope() : currentScope_DEPRECATED(); DfgScope* scope = FFlag::LuauDfgScopeStackNotNull ? currentScope() : currentScope_DEPRECATED();
// In order to avoid alias tracking, we need to clip the reference to the parent def. // 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)); DefId updated = defArena->freshCell(l->local, l->location, containsSubscriptedDefinition(incomingDef));
scope->bindings[l->local] = updated; scope->bindings[l->local] = updated;
@ -1275,7 +1303,10 @@ void DataFlowGraphBuilder::visitType(AstTypeFunction* f)
visitGenerics(f->generics); visitGenerics(f->generics);
visitGenericPacks(f->genericPacks); visitGenericPacks(f->genericPacks);
visitTypeList(f->argTypes); visitTypeList(f->argTypes);
visitTypeList(f->returnTypes); if (FFlag::LuauStoreReturnTypesAsPackOnAst)
visitTypePack(f->returnTypes);
else
visitTypeList(f->returnTypes_DEPRECATED);
} }
void DataFlowGraphBuilder::visitType(AstTypeTypeof* t) 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 diffFunction(DifferEnvironment& env, TypeId left, TypeId right);
static DifferResult diffGeneric(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 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 struct FindSeteqCounterexampleResult
{ {
// nullopt if no counterexample found // nullopt if no counterexample found
@ -481,14 +481,14 @@ static DifferResult diffNegation(DifferEnvironment& env, TypeId left, TypeId rig
return differResult; 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 ExternType* leftExternType = get<ExternType>(left);
const ClassType* rightClass = get<ClassType>(right); const ExternType* rightExternType = get<ExternType>(right);
LUAU_ASSERT(leftClass); LUAU_ASSERT(leftExternType);
LUAU_ASSERT(rightClass); LUAU_ASSERT(rightExternType);
if (leftClass == rightClass) if (leftExternType == rightExternType)
{ {
return DifferResult{}; return DifferResult{};
} }
@ -651,9 +651,9 @@ static DifferResult diffUsingEnv(DifferEnvironment& env, TypeId left, TypeId rig
{ {
return diffNegation(env, left, right); 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"}; throw InternalCompilerError{"Unimplemented Simple TypeId variant for diffing"};
@ -960,7 +960,7 @@ bool isSimple(TypeId ty)
{ {
ty = follow(ty); ty = follow(ty);
// TODO: think about GenericType, etc. // 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); 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 // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/BuiltinDefinitions.h" #include "Luau/BuiltinDefinitions.h"
LUAU_FASTFLAG(LuauDeclareExternType)
LUAU_FASTFLAG(LuauTypeFunOptional)
namespace Luau namespace Luau
{ {
@ -259,7 +262,37 @@ declare buffer: {
)BUILTIN_SRC"; )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 -- 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 declare class vector
@ -307,11 +340,11 @@ std::string getBuiltinDefinitionSource()
} }
// TODO: split into separate tagged unions when the new solver can appropriately handle that. // 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 = { export type type = {
tag: "nil" | "unknown" | "never" | "any" | "boolean" | "number" | "string" | "buffer" | "thread" | 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, is: (self: type, arg: string) -> boolean,
@ -358,6 +391,10 @@ export type type = {
ispack: (self: type) -> boolean, ispack: (self: type) -> boolean,
} }
)BUILTIN_SRC";
static const std::string kBuiltinDefinitionTypesLibSrc = R"BUILTIN_SRC(
declare types: { declare types: {
unknown: type, unknown: type,
never: type, never: type,
@ -377,12 +414,44 @@ declare types: {
newfunction: @checked (parameters: { head: {type}?, tail: type? }?, returns: { head: {type}?, tail: type? }?, generics: {type}?) -> type, newfunction: @checked (parameters: { head: {type}?, tail: type? }?, returns: { head: {type}?, tail: type? }?, generics: {type}?) -> type,
copy: @checked (arg: type) -> type, copy: @checked (arg: type) -> type,
} }
)BUILTIN_SRC"; )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() std::string getTypeFunctionDefinitionSource()
{ {
return kBuiltinDefinitionTypesSrc;
std::string result = kBuiltinDefinitionTypeMethodSrc;
if (FFlag::LuauTypeFunOptional)
result += kBuiltinDefinitionTypesLibWithOptionalSrc;
else
result += kBuiltinDefinitionTypesLibSrc;
return result;
} }
} // namespace Luau } // namespace Luau

View file

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

View file

@ -18,7 +18,7 @@
#include <unordered_set> #include <unordered_set>
LUAU_FASTINTVARIABLE(LuauIndentTypeMismatchMaxTypeLength, 10) LUAU_FASTINTVARIABLE(LuauIndentTypeMismatchMaxTypeLength, 10)
LUAU_FASTFLAG(LuauNonStrictFuncDefErrorFix) LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
static std::string wrongNumberOfArgsString( static std::string wrongNumberOfArgsString(
size_t expectedCount, size_t expectedCount,
@ -70,7 +70,7 @@ namespace Luau
{ {
// this list of binary operator type functions is used for better stringification of type functions errors // this list of binary operator type functions is used for better stringification of type functions errors
static const std::unordered_map<std::string, const char*> kBinaryOps{ static const std::unordered_map<std::string, const char*> DEPRECATED_kBinaryOps{
{"add", "+"}, {"add", "+"},
{"sub", "-"}, {"sub", "-"},
{"mul", "*"}, {"mul", "*"},
@ -86,12 +86,27 @@ static const std::unordered_map<std::string, const char*> kBinaryOps{
{"eq", "== or ~="} {"eq", "== or ~="}
}; };
static const std::unordered_map<std::string, const char*> kBinaryOps{
{"add", "+"},
{"sub", "-"},
{"mul", "*"},
{"div", "/"},
{"idiv", "//"},
{"pow", "^"},
{"mod", "%"},
{"concat", ".."},
{"lt", "< or >="},
{"le", "<= or >"},
{"eq", "== or ~="}
};
// this list of unary operator type functions is used for better stringification of type functions errors // this list of unary operator type functions is used for better stringification of type functions errors
static const std::unordered_map<std::string, const char*> kUnaryOps{{"unm", "-"}, {"len", "#"}, {"not", "not"}}; static const std::unordered_map<std::string, const char*> kUnaryOps{{"unm", "-"}, {"len", "#"}, {"not", "not"}};
// this list of type functions will receive a special error indicating that the user should file a bug on the GitHub repository // this list of type functions will receive a special error indicating that the user should file a bug on the GitHub repository
// putting a type function in this list indicates that it is expected to _always_ reduce // putting a type function in this list indicates that it is expected to _always_ reduce
static const std::unordered_set<std::string> kUnreachableTypeFunctions{"refine", "singleton", "union", "intersect"}; static const std::unordered_set<std::string> DEPRECATED_kUnreachableTypeFunctions{"refine", "singleton", "union", "intersect"};
static const std::unordered_set<std::string> kUnreachableTypeFunctions{"refine", "singleton", "union", "intersect", "and", "or"};
struct ErrorConverter struct ErrorConverter
{ {
@ -188,7 +203,7 @@ struct ErrorConverter
TypeId t = follow(e.table); TypeId t = follow(e.table);
if (get<TableType>(t)) if (get<TableType>(t))
return "Key '" + e.key + "' not found in table '" + Luau::toString(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) + "'"; return "Key '" + e.key + "' not found in class '" + Luau::toString(t) + "'";
else else
return "Type '" + Luau::toString(e.table) + "' does not have key '" + e.key + "'"; return "Type '" + Luau::toString(e.table) + "' does not have key '" + e.key + "'";
@ -356,7 +371,7 @@ struct ErrorConverter
std::string s = "Key '" + e.key + "' not found in "; std::string s = "Key '" + e.key + "' not found in ";
TypeId t = follow(e.table); TypeId t = follow(e.table);
if (get<ClassType>(t)) if (get<ExternType>(t))
s += "class"; s += "class";
else else
s += "table"; s += "table";
@ -387,8 +402,8 @@ struct ErrorConverter
std::optional<TypeId> metatable; std::optional<TypeId> metatable;
if (const MetatableType* mtType = get<MetatableType>(type)) if (const MetatableType* mtType = get<MetatableType>(type))
metatable = mtType->metatable; metatable = mtType->metatable;
else if (const ClassType* classType = get<ClassType>(type)) else if (const ExternType* externType = get<ExternType>(type))
metatable = classType->metatable; metatable = externType->metatable;
if (!metatable) if (!metatable)
return std::nullopt; return std::nullopt;
@ -596,7 +611,7 @@ struct ErrorConverter
return ss; 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"; return "Attempting a dynamic property access on type '" + Luau::toString(e.ty) + "' is unsafe and may cause exceptions at runtime";
} }
@ -643,7 +658,8 @@ struct ErrorConverter
} }
// binary operators // binary operators
if (auto binaryString = kBinaryOps.find(tfit->function->name); binaryString != kBinaryOps.end()) const auto binaryOps = FFlag::DebugLuauGreedyGeneralization ? kBinaryOps : DEPRECATED_kBinaryOps;
if (auto binaryString = binaryOps.find(tfit->function->name); binaryString != binaryOps.end())
{ {
std::string result = "Operator '" + std::string(binaryString->second) + "' could not be applied to operands of types "; std::string result = "Operator '" + std::string(binaryString->second) + "' could not be applied to operands of types ";
@ -697,7 +713,7 @@ struct ErrorConverter
"'"; "'";
} }
if (kUnreachableTypeFunctions.count(tfit->function->name)) if ((FFlag::DebugLuauGreedyGeneralization ? kUnreachableTypeFunctions : DEPRECATED_kUnreachableTypeFunctions).count(tfit->function->name))
{ {
return "Type function instance " + Luau::toString(e.ty) + " is uninhabited\n" + return "Type function instance " + Luau::toString(e.ty) + " is uninhabited\n" +
"This is likely to be a bug, please report it at https://github.com/luau-lang/luau/issues"; "This is likely to be a bug, please report it at https://github.com/luau-lang/luau/issues";
@ -756,7 +772,7 @@ struct ErrorConverter
std::string operator()(const NonStrictFunctionDefinitionError& e) const std::string operator()(const NonStrictFunctionDefinitionError& e) const
{ {
if (FFlag::LuauNonStrictFuncDefErrorFix && e.functionName.empty()) if (e.functionName.empty())
{ {
return "Argument " + e.argument + " with type '" + toString(e.argumentType) + "' is used in a way that will run time error"; return "Argument " + e.argument + " with type '" + toString(e.argumentType) + "' is used in a way that will run time error";
} }
@ -803,6 +819,11 @@ struct ErrorConverter
return e.message; return e.message;
} }
std::string operator()(const ReservedIdentifier& e) const
{
return e.name + " cannot be used as an identifier for a type function or alias";
}
std::string operator()(const CannotAssignToNever& e) const std::string operator()(const CannotAssignToNever& e) const
{ {
std::string result = "Cannot assign a value of type " + toString(e.rhsType) + " to a field of type never"; std::string result = "Cannot assign a value of type " + toString(e.rhsType) + " to a field of type never";
@ -1128,7 +1149,7 @@ bool TypePackMismatch::operator==(const TypePackMismatch& rhs) const
return *wantedTp == *rhs.wantedTp && *givenTp == *rhs.givenTp; 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; return ty == rhs.ty;
} }
@ -1190,6 +1211,11 @@ bool UserDefinedTypeFunctionError::operator==(const UserDefinedTypeFunctionError
return message == rhs.message; return message == rhs.message;
} }
bool ReservedIdentifier::operator==(const ReservedIdentifier& rhs) const
{
return name == rhs.name;
}
bool CannotAssignToNever::operator==(const CannotAssignToNever& rhs) const bool CannotAssignToNever::operator==(const CannotAssignToNever& rhs) const
{ {
if (cause.size() != rhs.cause.size()) if (cause.size() != rhs.cause.size())
@ -1365,7 +1391,7 @@ void copyError(T& e, TypeArena& destArena, CloneState& cloneState)
e.wantedTp = clone(e.wantedTp); e.wantedTp = clone(e.wantedTp);
e.givenTp = clone(e.givenTp); 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); e.ty = clone(e.ty);
else if constexpr (std::is_same_v<T, UninhabitedTypeFunction>) else if constexpr (std::is_same_v<T, UninhabitedTypeFunction>)
e.ty = clone(e.ty); e.ty = clone(e.ty);
@ -1409,6 +1435,9 @@ void copyError(T& e, TypeArena& destArena, CloneState& cloneState)
for (auto& ty : e.cause) for (auto& ty : e.cause)
ty = clone(ty); ty = clone(ty);
} }
else if constexpr (std::is_same_v<T, ReservedIdentifier>)
{
}
else else
static_assert(always_false_v<T>, "Non-exhaustive type switch"); static_assert(always_false_v<T>, "Non-exhaustive type switch");
} }

View file

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

View file

@ -27,22 +27,20 @@
LUAU_FASTINT(LuauTypeInferRecursionLimit); LUAU_FASTINT(LuauTypeInferRecursionLimit);
LUAU_FASTINT(LuauTypeInferIterationLimit); LUAU_FASTINT(LuauTypeInferIterationLimit);
LUAU_FASTINT(LuauTarjanChildLimit) LUAU_FASTINT(LuauTarjanChildLimit)
LUAU_FASTFLAG(LuauAutocompleteRefactorsForIncrementalAutocomplete)
LUAU_FASTFLAGVARIABLE(LuauMixedModeDefFinderTraversesTypeOf)
LUAU_FASTFLAGVARIABLE(LuauCloneIncrementalModule)
LUAU_FASTFLAGVARIABLE(DebugLogFragmentsFromAutocomplete) LUAU_FASTFLAGVARIABLE(DebugLogFragmentsFromAutocomplete)
LUAU_FASTFLAGVARIABLE(LuauBetterCursorInCommentDetection) LUAU_FASTFLAGVARIABLE(LuauBetterCursorInCommentDetection)
LUAU_FASTFLAGVARIABLE(LuauAllFreeTypesHaveScopes) LUAU_FASTFLAGVARIABLE(LuauAllFreeTypesHaveScopes)
LUAU_FASTFLAGVARIABLE(LuauPersistConstraintGenerationScopes) LUAU_FASTFLAGVARIABLE(LuauPersistConstraintGenerationScopes)
LUAU_FASTFLAGVARIABLE(LuauCloneTypeAliasBindings) LUAU_FASTFLAGVARIABLE(LuauCloneTypeAliasBindings)
LUAU_FASTFLAGVARIABLE(LuauCloneReturnTypePack)
LUAU_FASTFLAGVARIABLE(LuauIncrementalAutocompleteDemandBasedCloning) LUAU_FASTFLAGVARIABLE(LuauIncrementalAutocompleteDemandBasedCloning)
LUAU_FASTFLAG(LuauUserTypeFunTypecheck) LUAU_FASTFLAG(LuauUserTypeFunTypecheck)
LUAU_FASTFLAGVARIABLE(LuauFragmentNoTypeFunEval) LUAU_FASTFLAGVARIABLE(LuauFragmentNoTypeFunEval)
LUAU_FASTFLAGVARIABLE(LuauBetterScopeSelection) LUAU_FASTFLAGVARIABLE(LuauBetterScopeSelection)
LUAU_FASTFLAGVARIABLE(LuauBlockDiffFragmentSelection) LUAU_FASTFLAGVARIABLE(LuauBlockDiffFragmentSelection)
LUAU_FASTFLAGVARIABLE(LuauFragmentAcMemoryLeak) LUAU_FASTFLAGVARIABLE(LuauFragmentAcMemoryLeak)
LUAU_FASTFLAGVARIABLE(LuauGlobalVariableModuleIsolation)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
namespace namespace
{ {
@ -97,7 +95,11 @@ Location getFunctionDeclarationExtents(AstExprFunction* exprFn, AstExpr* exprNam
{ {
auto fnBegin = exprFn->location.begin; auto fnBegin = exprFn->location.begin;
auto fnEnd = exprFn->location.end; auto fnEnd = exprFn->location.end;
if (auto returnAnnot = exprFn->returnAnnotation) 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) if (returnAnnot->tailType)
fnEnd = returnAnnot->tailType->location.end; fnEnd = returnAnnot->tailType->location.end;
@ -542,6 +544,11 @@ struct UsageFinder : public AstVisitor
return true; return true;
} }
bool visit(AstTypePack* node) override
{
return FFlag::LuauStoreReturnTypesAsPackOnAst;
}
bool visit(AstStatTypeAlias* alias) override bool visit(AstStatTypeAlias* alias) override
{ {
declaredAliases.insert(std::string(alias->name.value)); declaredAliases.insert(std::string(alias->name.value));
@ -569,12 +576,32 @@ struct UsageFinder : public AstVisitor
return true; return true;
} }
bool visit(AstExprGlobal* global) override
{
if (FFlag::LuauGlobalVariableModuleIsolation)
globalDefsToPrePopulate.emplace_back(global->name, dfg->getDef(global));
return true;
}
bool visit(AstStatFunction* function) override
{
if (FFlag::LuauGlobalVariableModuleIsolation)
{
if (AstExprGlobal* g = function->name->as<AstExprGlobal>())
globalFunctionsReferenced.emplace_back(g->name);
}
return true;
}
NotNull<DataFlowGraph> dfg; NotNull<DataFlowGraph> dfg;
DenseHashSet<Name> declaredAliases{""}; DenseHashSet<Name> declaredAliases{""};
std::vector<std::pair<const Def*, AstLocal*>> localBindingsReferenced; std::vector<std::pair<const Def*, AstLocal*>> localBindingsReferenced;
DenseHashSet<const Def*> mentionedDefs{nullptr}; DenseHashSet<const Def*> mentionedDefs{nullptr};
std::vector<Name> referencedBindings{""}; std::vector<Name> referencedBindings{""};
std::vector<std::pair<Name, Name>> referencedImportedBindings{{"", ""}}; std::vector<std::pair<Name, Name>> referencedImportedBindings{{"", ""}};
std::vector<std::pair<AstName, const Def*>> globalDefsToPrePopulate;
std::vector<AstName> globalFunctionsReferenced;
}; };
// Runs the `UsageFinder` traversal on the fragment and grabs all of the types that are // Runs the `UsageFinder` traversal on the fragment and grabs all of the types that are
@ -648,7 +675,45 @@ void cloneTypesFromFragment(
} }
} }
// Finally - clone the returnType on the staleScope. This helps avoid potential leaks of free types. if (FFlag::LuauGlobalVariableModuleIsolation)
{
// Fourth - prepopulate the global function types
for (const auto& name : f.globalFunctionsReferenced)
{
if (auto ty = staleModule->getModuleScope()->lookup(name))
{
destScope->bindings[name] = Binding{Luau::cloneIncremental(*ty, *destArena, cloneState, destScope)};
}
else
{
TypeId bt = destArena->addType(BlockedType{});
destScope->bindings[name] = Binding{bt};
}
}
// Fifth - prepopulate the globals here
for (const auto& [name, def] : f.globalDefsToPrePopulate)
{
if (auto ty = staleModule->getModuleScope()->lookup(name))
{
destScope->lvalueTypes[def] = Luau::cloneIncremental(*ty, *destArena, cloneState, destScope);
}
else if (auto ty = destScope->lookup(name))
{
// This branch is a little strange - we are looking up a symbol in the destScope
// This scope has no parent pointer, and only cloned types are written to it, so this is a
// safe operation to do without cloning.
// The reason we do this, is the usage finder will traverse the global functions referenced first
// If there is no name associated with this function at the global scope, it must appear first in the fragment and we must
// create a blocked type for it. We write this blocked type directly into the `destScope` bindings
// Then when we go to traverse the `AstExprGlobal` associated with this function, we need to ensure that we map the def -> blockedType
// in `lvalueTypes`, which was previously written into `destScope`
destScope->lvalueTypes[def] = *ty;
}
}
}
// Finally, clone the returnType on the staleScope. This helps avoid potential leaks of free types.
if (staleScope->returnType) if (staleScope->returnType)
destScope->returnType = Luau::cloneIncremental(staleScope->returnType, *destArena, cloneState, destScope); destScope->returnType = Luau::cloneIncremental(staleScope->returnType, *destArena, cloneState, destScope);
} }
@ -670,7 +735,7 @@ struct MixedModeIncrementalTCDefFinder : public AstVisitor
// requires that we find the local/global `m` and place it in the environment. // 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 default behaviour here is to return false, and have individual visitors override
// the specific behaviour they need. // the specific behaviour they need.
return FFlag::LuauMixedModeDefFinderTraversesTypeOf; return true;
} }
bool visit(AstStatTypeAlias* alias) override bool visit(AstStatTypeAlias* alias) override
@ -820,7 +885,7 @@ void cloneAndSquashScopes(
} }
} }
if (FFlag::LuauCloneReturnTypePack && destScope->returnType) if (destScope->returnType)
destScope->returnType = Luau::cloneIncremental(destScope->returnType, *destArena, cloneState, destScope); destScope->returnType = Luau::cloneIncremental(destScope->returnType, *destArena, cloneState, destScope);
return; return;
@ -1160,31 +1225,6 @@ ModulePtr cloneModule(CloneState& cloneState, const ModulePtr& source, std::uniq
return incremental; 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( void mixedModeCompatibility(
const ScopePtr& bottomScopeStale, const ScopePtr& bottomScopeStale,
const ScopePtr& myFakeScope, const ScopePtr& myFakeScope,
@ -1248,10 +1288,8 @@ FragmentTypeCheckResult typecheckFragmentHelper_DEPRECATED(
ModulePtr incrementalModule = nullptr; ModulePtr incrementalModule = nullptr;
if (FFlag::LuauAllFreeTypesHaveScopes) if (FFlag::LuauAllFreeTypesHaveScopes)
incrementalModule = cloneModule(cloneState, stale, std::move(astAllocator), freshChildOfNearestScope.get()); incrementalModule = cloneModule(cloneState, stale, std::move(astAllocator), freshChildOfNearestScope.get());
else if (FFlag::LuauCloneIncrementalModule)
incrementalModule = cloneModule_DEPRECATED(cloneState, stale, std::move(astAllocator));
else else
incrementalModule = copyModule(stale, std::move(astAllocator)); incrementalModule = cloneModule_DEPRECATED(cloneState, stale, std::move(astAllocator));
reportWaypoint(reporter, FragmentAutocompleteWaypoint::CloneModuleEnd); reportWaypoint(reporter, FragmentAutocompleteWaypoint::CloneModuleEnd);
incrementalModule->checkedInNewSolver = true; incrementalModule->checkedInNewSolver = true;
@ -1305,8 +1343,6 @@ FragmentTypeCheckResult typecheckFragmentHelper_DEPRECATED(
}; };
reportWaypoint(reporter, FragmentAutocompleteWaypoint::CloneAndSquashScopeStart); reportWaypoint(reporter, FragmentAutocompleteWaypoint::CloneAndSquashScopeStart);
if (FFlag::LuauCloneIncrementalModule)
{
incrementalModule->scopes.emplace_back(root->location, freshChildOfNearestScope); incrementalModule->scopes.emplace_back(root->location, freshChildOfNearestScope);
cg.rootScope = freshChildOfNearestScope.get(); cg.rootScope = freshChildOfNearestScope.get();
@ -1327,22 +1363,7 @@ FragmentTypeCheckResult typecheckFragmentHelper_DEPRECATED(
for (auto p : cg.scopes) for (auto p : cg.scopes)
incrementalModule->scopes.emplace_back(std::move(p)); incrementalModule->scopes.emplace_back(std::move(p));
} }
}
else
{
// 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();
}
reportWaypoint(reporter, FragmentAutocompleteWaypoint::ConstraintSolverStart); reportWaypoint(reporter, FragmentAutocompleteWaypoint::ConstraintSolverStart);
if (FFlag::LuauAllFreeTypesHaveScopes) if (FFlag::LuauAllFreeTypesHaveScopes)
@ -1452,7 +1473,7 @@ FragmentTypeCheckResult typecheckFragment_(
SimplifierPtr simplifier = newSimplifier(NotNull{&incrementalModule->internalTypes}, frontend.builtinTypes); SimplifierPtr simplifier = newSimplifier(NotNull{&incrementalModule->internalTypes}, frontend.builtinTypes);
FrontendModuleResolver& resolver = getModuleResolver(frontend, opts); FrontendModuleResolver& resolver = getModuleResolver(frontend, opts);
std::shared_ptr<Scope> freshChildOfNearestScope = std::make_shared<Scope>(nullptr);
/// Contraint Generator /// Contraint Generator
ConstraintGenerator cg{ ConstraintGenerator cg{
incrementalModule, incrementalModule,
@ -1462,7 +1483,7 @@ FragmentTypeCheckResult typecheckFragment_(
NotNull{&resolver}, NotNull{&resolver},
frontend.builtinTypes, frontend.builtinTypes,
iceHandler, iceHandler,
stale->getModuleScope(), FFlag::LuauGlobalVariableModuleIsolation ? freshChildOfNearestScope : stale->getModuleScope(),
frontend.globals.globalTypeFunctionScope, frontend.globals.globalTypeFunctionScope,
nullptr, nullptr,
nullptr, nullptr,
@ -1471,7 +1492,6 @@ FragmentTypeCheckResult typecheckFragment_(
}; };
CloneState cloneState{frontend.builtinTypes}; CloneState cloneState{frontend.builtinTypes};
std::shared_ptr<Scope> freshChildOfNearestScope = std::make_shared<Scope>(nullptr);
incrementalModule->scopes.emplace_back(root->location, freshChildOfNearestScope); incrementalModule->scopes.emplace_back(root->location, freshChildOfNearestScope);
freshChildOfNearestScope->interiorFreeTypes.emplace(); freshChildOfNearestScope->interiorFreeTypes.emplace();
freshChildOfNearestScope->interiorFreeTypePacks.emplace(); freshChildOfNearestScope->interiorFreeTypePacks.emplace();
@ -1649,7 +1669,6 @@ FragmentAutocompleteResult fragmentAutocomplete(
IFragmentAutocompleteReporter* reporter IFragmentAutocompleteReporter* reporter
) )
{ {
LUAU_ASSERT(FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete);
LUAU_TIMETRACE_SCOPE("Luau::fragmentAutocomplete", "FragmentAutocomplete"); LUAU_TIMETRACE_SCOPE("Luau::fragmentAutocomplete", "FragmentAutocomplete");
LUAU_TIMETRACE_ARGUMENT("name", moduleName.c_str()); LUAU_TIMETRACE_ARGUMENT("name", moduleName.c_str());

View file

@ -40,13 +40,13 @@ LUAU_FASTFLAG(LuauInferInNoCheckMode)
LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3) LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauRethrowKnownExceptions, false) LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauRethrowKnownExceptions, false)
LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson) LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson)
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJsonFile) LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJsonFile)
LUAU_FASTFLAGVARIABLE(DebugLuauForbidInternalTypes) LUAU_FASTFLAGVARIABLE(DebugLuauForbidInternalTypes)
LUAU_FASTFLAGVARIABLE(DebugLuauForceStrictMode) LUAU_FASTFLAGVARIABLE(DebugLuauForceStrictMode)
LUAU_FASTFLAGVARIABLE(DebugLuauForceNonStrictMode) LUAU_FASTFLAGVARIABLE(DebugLuauForceNonStrictMode)
LUAU_FASTFLAGVARIABLE(LuauSelectivelyRetainDFGArena)
LUAU_FASTFLAG(LuauTypeFunResultInAutocomplete) LUAU_FASTFLAG(LuauTypeFunResultInAutocomplete)
namespace Luau namespace Luau
@ -129,9 +129,9 @@ static void generateDocumentationSymbols(TypeId ty, const std::string& rootName)
prop.documentationSymbol = rootName + "." + name; 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; prop.documentationSymbol = rootName + "." + name;
} }
@ -1003,11 +1003,8 @@ void Frontend::checkBuildQueueItem(BuildQueueItem& item)
freeze(module->interfaceTypes); freeze(module->interfaceTypes);
module->internalTypes.clear(); module->internalTypes.clear();
if (FFlag::LuauSelectivelyRetainDFGArena)
{
module->defArena.allocator.clear(); module->defArena.allocator.clear();
module->keyArena.allocator.clear(); module->keyArena.allocator.clear();
}
module->astTypes.clear(); module->astTypes.clear();
module->astTypePacks.clear(); module->astTypePacks.clear();
@ -1308,7 +1305,7 @@ ModulePtr check(
struct InternalTypeFinder : TypeOnceVisitor struct InternalTypeFinder : TypeOnceVisitor
{ {
bool visit(TypeId, const ClassType&) override bool visit(TypeId, const ExternType&) override
{ {
return false; return false;
} }
@ -1423,10 +1420,36 @@ ModulePtr check(
requireCycles requireCycles
}; };
// 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;
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); cg.visitModuleRoot(sourceModule.root);
result->errors = std::move(cg.errors); result->errors = std::move(cg.errors);
ConstraintSolver cs{ cs.emplace(
NotNull{&normalizer}, NotNull{&normalizer},
NotNull{simplifier.get()}, NotNull{simplifier.get()},
NotNull{&typeFunctionRuntime}, NotNull{&typeFunctionRuntime},
@ -1439,14 +1462,17 @@ ModulePtr check(
logger.get(), logger.get(),
NotNull{&dfg}, NotNull{&dfg},
limits limits
}; );
}
LUAU_ASSERT(bool(cs));
if (options.randomizeConstraintResolutionSeed) if (options.randomizeConstraintResolutionSeed)
cs.randomize(*options.randomizeConstraintResolutionSeed); cs->randomize(*options.randomizeConstraintResolutionSeed);
try try
{ {
cs.run(); cs->run();
} }
catch (const TimeLimitError&) catch (const TimeLimitError&)
{ {
@ -1466,12 +1492,12 @@ ModulePtr check(
printf("%s\n", output.c_str()); printf("%s\n", output.c_str());
} }
for (TypeError& e : cs.errors) for (TypeError& e : cs->errors)
result->errors.emplace_back(std::move(e)); result->errors.emplace_back(std::move(e));
result->scopes = std::move(cg.scopes); result->scopes = std::move(cg.scopes);
result->type = sourceModule.type; result->type = sourceModule.type;
result->upperBoundContributors = std::move(cs.upperBoundContributors); result->upperBoundContributors = std::move(cs->upperBoundContributors);
if (result->timeout || result->cancelled) if (result->timeout || result->cancelled)
{ {

View file

@ -11,11 +11,12 @@
#include "Luau/Type.h" #include "Luau/Type.h"
#include "Luau/TypeArena.h" #include "Luau/TypeArena.h"
#include "Luau/TypePack.h" #include "Luau/TypePack.h"
#include "Luau/Substitution.h"
#include "Luau/VisitType.h" #include "Luau/VisitType.h"
LUAU_FASTFLAG(LuauAutocompleteRefactorsForIncrementalAutocomplete) LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
LUAU_FASTFLAGVARIABLE(LuauNonReentrantGeneralization) LUAU_FASTFLAGVARIABLE(LuauNonReentrantGeneralization2)
namespace Luau namespace Luau
{ {
@ -468,7 +469,7 @@ struct FreeTypeSearcher : TypeVisitor
bool visit(TypeId ty, const FreeType& ft) override bool visit(TypeId ty, const FreeType& ft) override
{ {
if (FFlag::LuauNonReentrantGeneralization) if (FFlag::LuauNonReentrantGeneralization2)
{ {
if (!subsumes(scope, ft.scope)) if (!subsumes(scope, ft.scope))
return true; return true;
@ -519,7 +520,7 @@ struct FreeTypeSearcher : TypeVisitor
if ((tt.state == TableState::Free || tt.state == TableState::Unsealed) && subsumes(scope, tt.scope)) if ((tt.state == TableState::Free || tt.state == TableState::Unsealed) && subsumes(scope, tt.scope))
{ {
if (FFlag::LuauNonReentrantGeneralization) if (FFlag::LuauNonReentrantGeneralization2)
unsealedTables.insert(ty); unsealedTables.insert(ty);
else else
{ {
@ -547,7 +548,7 @@ struct FreeTypeSearcher : TypeVisitor
traverse(*prop.readTy); traverse(*prop.readTy);
else else
{ {
LUAU_ASSERT(prop.isShared() || FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete); LUAU_ASSERT(prop.isShared());
Polarity p = polarity; Polarity p = polarity;
polarity = Polarity::Mixed; polarity = Polarity::Mixed;
@ -558,7 +559,7 @@ struct FreeTypeSearcher : TypeVisitor
if (tt.indexer) if (tt.indexer)
{ {
if (FFlag::LuauNonReentrantGeneralization) if (FFlag::LuauNonReentrantGeneralization2)
{ {
// {[K]: V} is equivalent to three functions: get, set, and iterate // {[K]: V} is equivalent to three functions: get, set, and iterate
// //
@ -603,7 +604,7 @@ struct FreeTypeSearcher : TypeVisitor
return false; return false;
} }
bool visit(TypeId, const ClassType&) override bool visit(TypeId, const ExternType&) override
{ {
return false; return false;
} }
@ -616,7 +617,7 @@ struct FreeTypeSearcher : TypeVisitor
if (!subsumes(scope, ftp.scope)) if (!subsumes(scope, ftp.scope))
return true; return true;
if (FFlag::LuauNonReentrantGeneralization) if (FFlag::LuauNonReentrantGeneralization2)
{ {
GeneralizationParams<TypePackId>& params = typePacks[tp]; GeneralizationParams<TypePackId>& params = typePacks[tp];
++params.useCount; ++params.useCount;
@ -895,7 +896,7 @@ struct TypeCacher : TypeOnceVisitor
return false; return false;
} }
bool visit(TypeId ty, const ClassType&) override bool visit(TypeId ty, const ExternType&) override
{ {
cache(ty); cache(ty);
return false; return false;
@ -1092,73 +1093,95 @@ struct TypeCacher : TypeOnceVisitor
} }
}; };
/** struct RemoveType : Substitution // NOLINT
* Remove occurrences of `needle` within `haystack`. This is used to cull cyclic bounds from free types.
*
* @param haystack Either the upper or lower bound of a free type.
* @param needle The type to be removed.
*/
[[nodiscard]]
static TypeId removeType(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, DenseHashSet<TypeId>& seen, TypeId haystack, TypeId needle)
{ {
haystack = follow(haystack); NotNull<BuiltinTypes> builtinTypes;
TypeId needle;
if (seen.find(haystack)) RemoveType(NotNull<BuiltinTypes> builtinTypes, TypeArena* arena, TypeId needle)
return haystack; : Substitution(arena)
seen.insert(haystack); , builtinTypes(builtinTypes)
, needle(needle)
if (const UnionType* ut = get<UnionType>(haystack))
{ {
OrderedSet<TypeId> newOptions; }
for (TypeId option : ut) bool ignoreChildren(TypeId ty) override
{ {
if (option == needle) if (get<UnionType>(ty) || get<IntersectionType>(ty))
continue; return false;
if (get<NeverType>(option))
continue;
LUAU_ASSERT(!get<UnionType>(option));
if (get<IntersectionType>(option))
newOptions.insert(removeType(arena, builtinTypes, seen, option, needle));
else else
newOptions.insert(option); return true;
} }
if (newOptions.empty()) bool isDirty(TypeId ty) override
return builtinTypes->neverType;
else if (newOptions.size() == 1)
{ {
TypeId onlyType = *newOptions.begin(); // A union or intersection is dirty if it contains the needle or if it has any duplicate members.
LUAU_ASSERT(onlyType != haystack); if (auto ut = get<UnionType>(ty))
return onlyType; {
DenseHashSet<TypeId> distinctParts{nullptr};
size_t count = 0;
for (TypeId part : ut)
{
++count;
if (part == needle)
return true;
distinctParts.insert(follow(part));
} }
else return distinctParts.size() != count;
return arena->addType(UnionType{newOptions.takeVector()}); }
else if (auto it = get<IntersectionType>(ty))
{
DenseHashSet<TypeId> distinctParts{nullptr};
size_t count = 0;
for (TypeId part : it)
{
++count;
if (part == needle)
return true;
distinctParts.insert(follow(part));
}
return distinctParts.size() != count;
} }
if (const IntersectionType* it = get<IntersectionType>(haystack)) return false;
}
bool isDirty(TypePackId tp) override
{
return false;
}
TypeId clean(TypeId ty) override
{
if (auto ut = get<UnionType>(ty))
{ {
OrderedSet<TypeId> newParts; OrderedSet<TypeId> newParts;
for (TypeId part : it) for (TypeId ty : ut)
{ {
part = follow(part); if (ty != needle)
newParts.insert(ty);
}
if (part == needle) if (newParts.empty())
continue; return builtinTypes->neverType;
else if (newParts.size() == 1)
if (get<UnknownType>(part)) {
continue; TypeId onlyType = *newParts.begin();
LUAU_ASSERT(onlyType != needle);
LUAU_ASSERT(!get<IntersectionType>(follow(part))); return onlyType;
}
if (get<UnionType>(part))
newParts.insert(removeType(arena, builtinTypes, seen, part, needle));
else else
newParts.insert(part); return arena->addType(UnionType{newParts.takeVector()});
}
else if (auto it = get<IntersectionType>(ty))
{
OrderedSet<TypeId> newParts;
for (TypeId ty : it)
{
if (ty != needle)
newParts.insert(ty);
} }
if (newParts.empty()) if (newParts.empty())
@ -1172,11 +1195,31 @@ static TypeId removeType(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtin
else else
return arena->addType(IntersectionType{newParts.takeVector()}); return arena->addType(IntersectionType{newParts.takeVector()});
} }
else
return haystack; return ty;
} }
std::optional<TypeId> generalizeType( TypePackId clean(TypePackId tp) override
{
return tp;
}
};
/**
* Remove occurrences of `needle` within `haystack`. This is used to cull cyclic bounds from free types.
*
* @param haystack Either the upper or lower bound of a free type.
* @param needle The type to be removed.
*/
[[nodiscard]]
static std::optional<
TypeId> removeType(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, TypeId haystack, TypeId needle)
{
RemoveType rt{builtinTypes, arena, needle};
return rt.substitute(haystack);
}
GeneralizationResult<TypeId> generalizeType(
NotNull<TypeArena> arena, NotNull<TypeArena> arena,
NotNull<BuiltinTypes> builtinTypes, NotNull<BuiltinTypes> builtinTypes,
NotNull<Scope> scope, NotNull<Scope> scope,
@ -1189,7 +1232,7 @@ std::optional<TypeId> generalizeType(
FreeType* ft = getMutable<FreeType>(freeTy); FreeType* ft = getMutable<FreeType>(freeTy);
LUAU_ASSERT(ft); LUAU_ASSERT(ft);
LUAU_ASSERT(isPositive(params.polarity) || isNegative(params.polarity)); LUAU_ASSERT(isKnown(params.polarity));
const bool hasLowerBound = !get<NeverType>(follow(ft->lowerBound)); const bool hasLowerBound = !get<NeverType>(follow(ft->lowerBound));
const bool hasUpperBound = !get<UnknownType>(follow(ft->upperBound)); const bool hasUpperBound = !get<UnknownType>(follow(ft->upperBound));
@ -1198,12 +1241,12 @@ std::optional<TypeId> generalizeType(
if (!hasLowerBound && !hasUpperBound) if (!hasLowerBound && !hasUpperBound)
{ {
if ((params.polarity != Polarity::Mixed && params.useCount == 1) || !isWithinFunction) if (!isWithinFunction || (!FFlag::DebugLuauGreedyGeneralization && (params.polarity != Polarity::Mixed && params.useCount == 1)))
emplaceType<BoundType>(asMutable(freeTy), builtinTypes->unknownType); emplaceType<BoundType>(asMutable(freeTy), builtinTypes->unknownType);
else else
{ {
emplaceType<GenericType>(asMutable(freeTy), scope, params.polarity); emplaceType<GenericType>(asMutable(freeTy), scope, params.polarity);
return freeTy; return {freeTy, /*wasReplacedByGeneric*/ true};
} }
} }
// It is possible that this free type has other free types in its upper // It is possible that this free type has other free types in its upper
@ -1219,20 +1262,24 @@ std::optional<TypeId> generalizeType(
lowerFree->upperBound = builtinTypes->unknownType; lowerFree->upperBound = builtinTypes->unknownType;
else else
{ {
DenseHashSet<TypeId> replaceSeen{nullptr}; std::optional<TypeId> removed = removeType(arena, builtinTypes, lb, freeTy);
lb = removeType(arena, builtinTypes, replaceSeen, lb, freeTy); if (removed)
lb = *removed;
else
return {std::nullopt, false, /*resourceLimitsExceeded*/ true};
ft->lowerBound = lb; ft->lowerBound = lb;
} }
if (follow(lb) != freeTy) if (follow(lb) != freeTy)
emplaceType<BoundType>(asMutable(freeTy), lb); emplaceType<BoundType>(asMutable(freeTy), lb);
else if (!isWithinFunction || params.useCount == 1) else if (!isWithinFunction || (!FFlag::DebugLuauGreedyGeneralization && params.useCount == 1))
emplaceType<BoundType>(asMutable(freeTy), builtinTypes->unknownType); emplaceType<BoundType>(asMutable(freeTy), builtinTypes->unknownType);
else else
{ {
// if the lower bound is the type in question (eg 'a <: 'a), we don't actually have a lower bound. // if the lower bound is the type in question (eg 'a <: 'a), we don't actually have a lower bound.
emplaceType<GenericType>(asMutable(freeTy), scope, params.polarity); emplaceType<GenericType>(asMutable(freeTy), scope, params.polarity);
return freeTy; return {freeTy, /*wasReplacedByGeneric*/ true};
} }
} }
else else
@ -1243,8 +1290,11 @@ std::optional<TypeId> generalizeType(
else else
{ {
// If the free type appears within its own upper bound, cull that cycle. // If the free type appears within its own upper bound, cull that cycle.
DenseHashSet<TypeId> replaceSeen{nullptr}; std::optional<TypeId> removed = removeType(arena, builtinTypes, ub, freeTy);
ub = removeType(arena, builtinTypes, replaceSeen, ub, freeTy); if (removed)
ub = *removed;
else
return {std::nullopt, false, /*resourceLimitsExceeded*/ true};
ft->upperBound = ub; ft->upperBound = ub;
} }
@ -1256,14 +1306,14 @@ std::optional<TypeId> generalizeType(
{ {
// if the upper bound is the type in question, we don't actually have an upper bound. // if the upper bound is the type in question, we don't actually have an upper bound.
emplaceType<GenericType>(asMutable(freeTy), scope, params.polarity); emplaceType<GenericType>(asMutable(freeTy), scope, params.polarity);
return freeTy; return {freeTy, /*wasReplacedByGeneric*/ true};
} }
} }
return std::nullopt; return {freeTy, /*wasReplacedByGeneric*/ false};
} }
std::optional<TypePackId> generalizeTypePack( GeneralizationResult<TypePackId> generalizeTypePack(
NotNull<TypeArena> arena, NotNull<TypeArena> arena,
NotNull<BuiltinTypes> builtinTypes, NotNull<BuiltinTypes> builtinTypes,
NotNull<Scope> scope, NotNull<Scope> scope,
@ -1274,24 +1324,24 @@ std::optional<TypePackId> generalizeTypePack(
tp = follow(tp); tp = follow(tp);
if (tp->owningArena != arena) if (tp->owningArena != arena)
return std::nullopt; return {tp, /*wasReplacedByGeneric*/ false};
const FreeTypePack* ftp = get<FreeTypePack>(tp); const FreeTypePack* ftp = get<FreeTypePack>(tp);
if (!ftp) if (!ftp)
return std::nullopt; return {tp, /*wasReplacedByGeneric*/ false};
if (!subsumes(scope, ftp->scope)) if (!subsumes(scope, ftp->scope))
return std::nullopt; return {tp, /*wasReplacedByGeneric*/ false};
if (1 == params.useCount) if (1 == params.useCount)
emplaceTypePack<BoundTypePack>(asMutable(tp), builtinTypes->unknownTypePack); emplaceTypePack<BoundTypePack>(asMutable(tp), builtinTypes->unknownTypePack);
else else
{ {
emplaceTypePack<GenericTypePack>(asMutable(tp), scope, params.polarity); emplaceTypePack<GenericTypePack>(asMutable(tp), scope, params.polarity);
return tp; return {tp, /*wasReplacedByGeneric*/ true};
} }
return std::nullopt; return {tp, /*wasReplacedByGeneric*/ false};
} }
void sealTable(NotNull<Scope> scope, TypeId ty) void sealTable(NotNull<Scope> scope, TypeId ty)
@ -1312,7 +1362,8 @@ std::optional<TypeId> generalize(
NotNull<BuiltinTypes> builtinTypes, NotNull<BuiltinTypes> builtinTypes,
NotNull<Scope> scope, NotNull<Scope> scope,
NotNull<DenseHashSet<TypeId>> cachedTypes, NotNull<DenseHashSet<TypeId>> cachedTypes,
TypeId ty TypeId ty,
std::optional<TypeId> generalizationTarget
) )
{ {
ty = follow(ty); ty = follow(ty);
@ -1323,7 +1374,7 @@ std::optional<TypeId> generalize(
FreeTypeSearcher fts{scope, cachedTypes}; FreeTypeSearcher fts{scope, cachedTypes};
fts.traverse(ty); fts.traverse(ty);
if (FFlag::LuauNonReentrantGeneralization) if (FFlag::LuauNonReentrantGeneralization2)
{ {
FunctionType* functionTy = getMutable<FunctionType>(ty); FunctionType* functionTy = getMutable<FunctionType>(ty);
auto pushGeneric = [&](TypeId t) auto pushGeneric = [&](TypeId t)
@ -1340,21 +1391,37 @@ std::optional<TypeId> generalize(
for (const auto& [freeTy, params] : fts.types) for (const auto& [freeTy, params] : fts.types)
{ {
if (std::optional<TypeId> genericTy = generalizeType(arena, builtinTypes, scope, freeTy, params)) if (!generalizationTarget || freeTy == *generalizationTarget)
pushGeneric(*genericTy); {
GeneralizationResult<TypeId> res = generalizeType(arena, builtinTypes, scope, freeTy, params);
if (res.resourceLimitsExceeded)
return std::nullopt;
if (res && res.wasReplacedByGeneric)
pushGeneric(*res.result);
}
} }
for (TypeId unsealedTableTy : fts.unsealedTables) for (TypeId unsealedTableTy : fts.unsealedTables)
{
if (!generalizationTarget || unsealedTableTy == *generalizationTarget)
sealTable(scope, unsealedTableTy); sealTable(scope, unsealedTableTy);
}
for (const auto& [freePackId, params] : fts.typePacks) for (const auto& [freePackId, params] : fts.typePacks)
{ {
TypePackId freePack = follow(freePackId); TypePackId freePack = follow(freePackId);
std::optional<TypePackId> generalizedTp = generalizeTypePack(arena, builtinTypes, scope, freePack, params); if (!generalizationTarget)
{
GeneralizationResult<TypePackId> generalizedTp = generalizeTypePack(arena, builtinTypes, scope, freePack, params);
if (generalizedTp) if (generalizedTp.resourceLimitsExceeded)
return std::nullopt;
if (generalizedTp && generalizedTp.wasReplacedByGeneric)
pushGenericPack(freePack); pushGenericPack(freePack);
} }
}
TypeCacher cacher{cachedTypes}; TypeCacher cacher{cachedTypes};
cacher.traverse(ty); cacher.traverse(ty);
@ -1397,4 +1464,197 @@ std::optional<TypeId> generalize(
return ty; return ty;
} }
struct GenericCounter : TypeVisitor
{
struct CounterState
{
size_t count = 0;
Polarity polarity = Polarity::None;
};
NotNull<DenseHashSet<TypeId>> cachedTypes;
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
{
auto state = generics.find(ty);
if (state)
{
++state->count;
state->polarity |= polarity;
}
return false;
}
bool visit(TypePackId tp, const GenericTypePack&) override
{
auto state = genericPacks.find(tp);
if (state)
{
++state->count;
state->polarity |= polarity;
}
return false;
}
};
void pruneUnnecessaryGenerics(
NotNull<TypeArena> arena,
NotNull<BuiltinTypes> builtinTypes,
NotNull<Scope> scope,
NotNull<DenseHashSet<TypeId>> cachedTypes,
TypeId ty
)
{
if (!FFlag::DebugLuauGreedyGeneralization)
return;
ty = follow(ty);
if (ty->owningArena != arena || ty->persistent)
return;
FunctionType* functionTy = getMutable<FunctionType>(ty);
if (!functionTy)
return;
// 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);
if (g && !g->explicitName)
counter.generics[generic] = {};
}
// 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)
{
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, state] : counter.generics)
{
if (state.count == 1 && state.polarity != Polarity::Mixed)
emplaceType<BoundType>(asMutable(generic), builtinTypes->unknownType);
}
// 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)
{
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, state] : counter.genericPacks)
{
if (state.count == 1)
emplaceTypePack<BoundTypePack>(asMutable(genericPack), builtinTypes->unknownTypePack);
}
DenseHashSet<TypePackId> seen2{nullptr};
auto it2 = std::remove_if(
functionTy->genericPacks.begin(),
functionTy->genericPacks.end(),
[&](TypePackId tp)
{
tp = follow(tp);
if (seen2.contains(tp))
return true;
seen2.insert(tp);
return !get<GenericTypePack>(tp);
}
);
functionTy->genericPacks.erase(it2, functionTy->genericPacks.end());
}
} // namespace Luau } // namespace Luau

View file

@ -5,7 +5,7 @@
#include "Luau/Scope.h" #include "Luau/Scope.h"
#include "Luau/VisitType.h" #include "Luau/VisitType.h"
LUAU_FASTFLAG(LuauNonReentrantGeneralization) LUAU_FASTFLAG(LuauNonReentrantGeneralization2)
namespace Luau namespace Luau
{ {
@ -95,16 +95,16 @@ struct InferPolarity : TypeVisitor
// types. // types.
for (TypeId generic : ft.generics) for (TypeId generic : ft.generics)
{ {
generic = follow(generic);
const auto gen = get<GenericType>(generic); const auto gen = get<GenericType>(generic);
LUAU_ASSERT(gen); if (gen && subsumes(scope, gen->scope))
if (subsumes(scope, gen->scope))
types[generic] = Polarity::None; types[generic] = Polarity::None;
} }
for (TypePackId genericPack : ft.genericPacks) for (TypePackId genericPack : ft.genericPacks)
{ {
genericPack = follow(genericPack);
const auto gen = get<GenericTypePack>(genericPack); const auto gen = get<GenericTypePack>(genericPack);
LUAU_ASSERT(gen); if (gen && subsumes(scope, gen->scope))
if (subsumes(scope, gen->scope))
packs[genericPack] = Polarity::None; packs[genericPack] = Polarity::None;
} }
@ -118,7 +118,7 @@ struct InferPolarity : TypeVisitor
return false; return false;
} }
bool visit(TypeId, const ClassType&) override bool visit(TypeId, const ExternType&) override
{ {
return false; return false;
} }
@ -133,7 +133,7 @@ struct InferPolarity : TypeVisitor
template<typename TID> template<typename TID>
static void inferGenericPolarities_(NotNull<TypeArena> arena, NotNull<Scope> scope, TID ty) static void inferGenericPolarities_(NotNull<TypeArena> arena, NotNull<Scope> scope, TID ty)
{ {
if (!FFlag::LuauNonReentrantGeneralization) if (!FFlag::LuauNonReentrantGeneralization2)
return; return;
InferPolarity infer{arena, scope}; InferPolarity infer{arena, scope};

View file

@ -11,7 +11,6 @@
#include <algorithm> #include <algorithm>
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
namespace Luau namespace Luau
{ {
@ -50,7 +49,7 @@ bool Instantiation::ignoreChildren(TypeId ty)
{ {
if (log->getMutable<FunctionType>(ty)) if (log->getMutable<FunctionType>(ty))
return true; return true;
else if (get<ClassType>(ty)) else if (get<ExternType>(ty))
return true; return true;
else else
return false; 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. // 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); return (!generics.empty() || !genericPacks.empty()) && (ftv->generics == generics) && (ftv->genericPacks == genericPacks);
} }
else if (get<ClassType>(ty)) else if (get<ExternType>(ty))
return true; return true;
else else
{ {
@ -164,7 +163,7 @@ TypeId ReplaceGenerics::clean(TypeId ty)
} }
else 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) bool Instantiation2::ignoreChildren(TypeId ty)
{ {
if (get<ClassType>(ty)) if (get<ExternType>(ty))
return true; return true;
if (auto ftv = get<FunctionType>(ty)) if (auto ftv = get<FunctionType>(ty))

View file

@ -193,8 +193,8 @@ static void errorToString(std::ostream& stream, const T& err)
stream << "NormalizationTooComplex { }"; stream << "NormalizationTooComplex { }";
else if constexpr (std::is_same_v<T, TypePackMismatch>) else if constexpr (std::is_same_v<T, TypePackMismatch>)
stream << "TypePackMismatch { wanted = '" + toString(err.wantedTp) + "', given = '" + toString(err.givenTp) + "' }"; stream << "TypePackMismatch { wanted = '" + toString(err.wantedTp) + "', given = '" + toString(err.givenTp) + "' }";
else if constexpr (std::is_same_v<T, DynamicPropertyLookupOnClassesUnsafe>) else if constexpr (std::is_same_v<T, DynamicPropertyLookupOnExternTypesUnsafe>)
stream << "DynamicPropertyLookupOnClassesUnsafe { " << toString(err.ty) << " }"; stream << "DynamicPropertyLookupOnExternTypesUnsafe { " << toString(err.ty) << " }";
else if constexpr (std::is_same_v<T, UninhabitedTypeFunction>) else if constexpr (std::is_same_v<T, UninhabitedTypeFunction>)
stream << "UninhabitedTypeFunction { " << toString(err.ty) << " }"; stream << "UninhabitedTypeFunction { " << toString(err.ty) << " }";
else if constexpr (std::is_same_v<T, ExplicitFunctionAnnotationRecommended>) else if constexpr (std::is_same_v<T, ExplicitFunctionAnnotationRecommended>)
@ -229,6 +229,8 @@ static void errorToString(std::ostream& stream, const T& err)
stream << "UnexpectedTypePackInSubtyping { tp = '" + toString(err.tp) + "' }"; stream << "UnexpectedTypePackInSubtyping { tp = '" + toString(err.tp) + "' }";
else if constexpr (std::is_same_v<T, UserDefinedTypeFunctionError>) else if constexpr (std::is_same_v<T, UserDefinedTypeFunctionError>)
stream << "UserDefinedTypeFunctionError { " << err.message << " }"; stream << "UserDefinedTypeFunctionError { " << err.message << " }";
else if constexpr (std::is_same_v<T, ReservedIdentifier>)
stream << "ReservedIdentifier { " << err.name << " }";
else if constexpr (std::is_same_v<T, CannotAssignToNever>) else if constexpr (std::is_same_v<T, CannotAssignToNever>)
{ {
stream << "CannotAssignToNever { rvalueType = '" << toString(err.rhsType) << "', reason = '" << err.reason << "', cause = { "; stream << "CannotAssignToNever { rvalueType = '" << toString(err.rhsType) << "', reason = '" << err.reason << "', cause = { ";

View file

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

View file

@ -20,10 +20,12 @@
#include <iostream> #include <iostream>
#include <iterator> #include <iterator>
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds) LUAU_FASTFLAG(DebugLuauMagicTypes)
LUAU_FASTFLAGVARIABLE(LuauNonStrictVisitorImprovements) LUAU_FASTFLAGVARIABLE(LuauNonStrictVisitorImprovements)
LUAU_FASTFLAGVARIABLE(LuauNewNonStrictWarnOnUnknownGlobals) LUAU_FASTFLAGVARIABLE(LuauNewNonStrictWarnOnUnknownGlobals)
LUAU_FASTFLAGVARIABLE(LuauNonStrictFuncDefErrorFix) LUAU_FASTFLAGVARIABLE(LuauNewNonStrictVisitTypes2)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
namespace Luau namespace Luau
{ {
@ -215,7 +217,7 @@ struct NonStrictTypeChecker
return *fst; return *fst;
else if (auto ftp = get<FreeTypePack>(pack)) 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}); TypePackId freeTail = arena->addTypePack(FreeTypePack{ftp->scope});
TypePack* resultPack = emplaceTypePack<TypePack>(asMutable(pack)); TypePack* resultPack = emplaceTypePack<TypePack>(asMutable(pack));
@ -309,7 +311,7 @@ struct NonStrictTypeChecker
return visit(s); return visit(s);
else if (auto s = stat->as<AstStatDeclareGlobal>()) else if (auto s = stat->as<AstStatDeclareGlobal>())
return visit(s); return visit(s);
else if (auto s = stat->as<AstStatDeclareClass>()) else if (auto s = stat->as<AstStatDeclareExternType>())
return visit(s); return visit(s);
else if (auto s = stat->as<AstStatError>()) else if (auto s = stat->as<AstStatError>())
return visit(s); return visit(s);
@ -335,7 +337,12 @@ struct NonStrictTypeChecker
// local x ; B generates the context of B without x // local x ; B generates the context of B without x
visit(local); visit(local);
for (auto local : local->vars) for (auto local : local->vars)
{
ctx.remove(dfg->getDef(local)); ctx.remove(dfg->getDef(local));
if (FFlag::LuauNewNonStrictVisitTypes2)
visit(local->annotation);
}
} }
else else
ctx = NonStrictContext::disjunction(builtinTypes, arena, visit(stat), ctx); ctx = NonStrictContext::disjunction(builtinTypes, arena, visit(stat), ctx);
@ -420,6 +427,9 @@ struct NonStrictTypeChecker
NonStrictContext visit(AstStatFor* forStatement) NonStrictContext visit(AstStatFor* forStatement)
{ {
if (FFlag::LuauNewNonStrictVisitTypes2)
visit(forStatement->var->annotation);
if (FFlag::LuauNonStrictVisitorImprovements) if (FFlag::LuauNonStrictVisitorImprovements)
{ {
// TODO: throwing out context based on same principle as existing code? // TODO: throwing out context based on same principle as existing code?
@ -439,6 +449,12 @@ struct NonStrictTypeChecker
NonStrictContext visit(AstStatForIn* forInStatement) NonStrictContext visit(AstStatForIn* forInStatement)
{ {
if (FFlag::LuauNewNonStrictVisitTypes2)
{
for (auto var : forInStatement->vars)
visit(var->annotation);
}
if (FFlag::LuauNonStrictVisitorImprovements) if (FFlag::LuauNonStrictVisitorImprovements)
{ {
for (AstExpr* rhs : forInStatement->values) for (AstExpr* rhs : forInStatement->values)
@ -487,6 +503,12 @@ struct NonStrictTypeChecker
NonStrictContext visit(AstStatTypeAlias* typeAlias) NonStrictContext visit(AstStatTypeAlias* typeAlias)
{ {
if (FFlag::LuauNewNonStrictVisitTypes2)
{
visitGenerics(typeAlias->generics, typeAlias->genericPacks);
visit(typeAlias->type);
}
return {}; return {};
} }
@ -497,16 +519,38 @@ struct NonStrictTypeChecker
NonStrictContext visit(AstStatDeclareFunction* declFn) NonStrictContext visit(AstStatDeclareFunction* declFn)
{ {
if (FFlag::LuauNewNonStrictVisitTypes2)
{
visitGenerics(declFn->generics, declFn->genericPacks);
visit(declFn->params);
visit(declFn->retTypes);
}
return {}; return {};
} }
NonStrictContext visit(AstStatDeclareGlobal* declGlobal) NonStrictContext visit(AstStatDeclareGlobal* declGlobal)
{ {
if (FFlag::LuauNewNonStrictVisitTypes2)
visit(declGlobal->type);
return {}; return {};
} }
NonStrictContext visit(AstStatDeclareClass* declClass) NonStrictContext visit(AstStatDeclareExternType* declClass)
{ {
if (FFlag::LuauNewNonStrictVisitTypes2)
{
if (declClass->indexer)
{
visit(declClass->indexer->indexType);
visit(declClass->indexer->resultType);
}
for (auto prop : declClass->props)
visit(prop.ty);
}
return {}; return {};
} }
@ -765,19 +809,32 @@ struct NonStrictTypeChecker
for (AstLocal* local : exprFn->args) for (AstLocal* local : exprFn->args)
{ {
if (std::optional<TypeId> ty = willRunTimeErrorFunctionDefinition(local, remainder)) if (std::optional<TypeId> ty = willRunTimeErrorFunctionDefinition(local, remainder))
{
if (FFlag::LuauNonStrictFuncDefErrorFix)
{ {
const char* debugname = exprFn->debugname.value; const char* debugname = exprFn->debugname.value;
reportError(NonStrictFunctionDefinitionError{debugname ? debugname : "", local->name.value, *ty}, local->location); reportError(NonStrictFunctionDefinitionError{debugname ? debugname : "", local->name.value, *ty}, local->location);
} }
remainder.remove(dfg->getDef(local));
if (FFlag::LuauNewNonStrictVisitTypes2)
visit(local->annotation);
}
if (FFlag::LuauNewNonStrictVisitTypes2)
{
visitGenerics(exprFn->generics, exprFn->genericPacks);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
visit(exprFn->returnAnnotation);
else else
{ {
reportError(NonStrictFunctionDefinitionError{exprFn->debugname.value, local->name.value, *ty}, local->location); if (exprFn->returnAnnotation_DEPRECATED)
visit(*exprFn->returnAnnotation_DEPRECATED);
} }
if (exprFn->varargAnnotation)
visit(exprFn->varargAnnotation);
} }
remainder.remove(dfg->getDef(local));
}
return remainder; return remainder;
} }
@ -818,6 +875,9 @@ struct NonStrictTypeChecker
NonStrictContext visit(AstExprTypeAssertion* typeAssertion) NonStrictContext visit(AstExprTypeAssertion* typeAssertion)
{ {
if (FFlag::LuauNewNonStrictVisitTypes2)
visit(typeAssertion->annotation);
if (FFlag::LuauNonStrictVisitorImprovements) if (FFlag::LuauNonStrictVisitorImprovements)
return visit(typeAssertion->expr, ValueContext::RValue); return visit(typeAssertion->expr, ValueContext::RValue);
else else
@ -854,6 +914,331 @@ struct NonStrictTypeChecker
return {}; return {};
} }
void visit(AstType* ty)
{
LUAU_ASSERT(FFlag::LuauNewNonStrictVisitTypes2);
// If this node is `nullptr`, early exit.
if (!ty)
return;
if (auto t = ty->as<AstTypeReference>())
return visit(t);
else if (auto t = ty->as<AstTypeTable>())
return visit(t);
else if (auto t = ty->as<AstTypeFunction>())
return visit(t);
else if (auto t = ty->as<AstTypeTypeof>())
return visit(t);
else if (auto t = ty->as<AstTypeUnion>())
return visit(t);
else if (auto t = ty->as<AstTypeIntersection>())
return visit(t);
else if (auto t = ty->as<AstTypeGroup>())
return visit(t->type);
}
void visit(AstTypeReference* ty)
{
// No further validation is necessary in this case. The main logic for
// _luau_print is contained in lookupAnnotation.
if (FFlag::DebugLuauMagicTypes && ty->name == "_luau_print")
return;
for (const AstTypeOrPack& param : ty->parameters)
{
if (param.type)
visit(param.type);
else
visit(param.typePack);
}
Scope* scope = findInnermostScope(ty->location);
LUAU_ASSERT(scope);
std::optional<TypeFun> alias = ty->prefix ? scope->lookupImportedType(ty->prefix->value, ty->name.value) : scope->lookupType(ty->name.value);
if (alias.has_value())
{
size_t typesRequired = alias->typeParams.size();
size_t packsRequired = alias->typePackParams.size();
bool hasDefaultTypes = std::any_of(
alias->typeParams.begin(),
alias->typeParams.end(),
[](auto&& el)
{
return el.defaultValue.has_value();
}
);
bool hasDefaultPacks = std::any_of(
alias->typePackParams.begin(),
alias->typePackParams.end(),
[](auto&& el)
{
return el.defaultValue.has_value();
}
);
if (!ty->hasParameterList)
{
if ((!alias->typeParams.empty() && !hasDefaultTypes) || (!alias->typePackParams.empty() && !hasDefaultPacks))
reportError(GenericError{"Type parameter list is required"}, ty->location);
}
size_t typesProvided = 0;
size_t extraTypes = 0;
size_t packsProvided = 0;
for (const AstTypeOrPack& p : ty->parameters)
{
if (p.type)
{
if (packsProvided != 0)
{
reportError(GenericError{"Type parameters must come before type pack parameters"}, ty->location);
continue;
}
if (typesProvided < typesRequired)
typesProvided += 1;
else
extraTypes += 1;
}
else if (p.typePack)
{
std::optional<TypePackId> tp = lookupPackAnnotation(p.typePack);
if (!tp.has_value())
continue;
if (typesProvided < typesRequired && size(*tp) == 1 && finite(*tp) && first(*tp))
typesProvided += 1;
else
packsProvided += 1;
}
}
if (extraTypes != 0 && packsProvided == 0)
{
// Extra types are only collected into a pack if a pack is expected
if (packsRequired != 0)
packsProvided += 1;
else
typesProvided += extraTypes;
}
for (size_t i = typesProvided; i < typesRequired; ++i)
{
if (alias->typeParams[i].defaultValue)
typesProvided += 1;
}
for (size_t i = packsProvided; i < packsRequired; ++i)
{
if (alias->typePackParams[i].defaultValue)
packsProvided += 1;
}
if (extraTypes == 0 && packsProvided + 1 == packsRequired)
packsProvided += 1;
if (typesProvided != typesRequired || packsProvided != packsRequired)
{
reportError(
IncorrectGenericParameterCount{
/* name */ ty->name.value,
/* typeFun */ *alias,
/* actualParameters */ typesProvided,
/* actualPackParameters */ packsProvided,
},
ty->location
);
}
}
else
{
if (scope->lookupPack(ty->name.value))
{
reportError(
SwappedGenericTypeParameter{
ty->name.value,
SwappedGenericTypeParameter::Kind::Type,
},
ty->location
);
}
else
{
std::string symbol = "";
if (ty->prefix)
{
symbol += (*(ty->prefix)).value;
symbol += ".";
}
symbol += ty->name.value;
reportError(UnknownSymbol{symbol, UnknownSymbol::Context::Type}, ty->location);
}
}
}
void visit(AstTypeTable* table)
{
if (table->indexer)
{
visit(table->indexer->indexType);
visit(table->indexer->resultType);
}
for (auto prop : table->props)
visit(prop.type);
}
void visit(AstTypeFunction* function)
{
visit(function->argTypes);
visit(function->returnTypes);
}
void visit(AstTypeTypeof* typeOf)
{
visit(typeOf->expr, ValueContext::RValue);
}
void visit(AstTypeUnion* unionType)
{
for (auto typ : unionType->types)
visit(typ);
}
void visit(AstTypeIntersection* intersectionType)
{
for (auto typ : intersectionType->types)
visit(typ);
}
void visit(AstTypeList& list)
{
for (auto typ : list.types)
visit(typ);
if (list.tailType)
visit(list.tailType);
}
void visit(AstTypePack* pack)
{
LUAU_ASSERT(FFlag::LuauNewNonStrictVisitTypes2);
// If there is no pack node, early exit.
if (!pack)
return;
if (auto p = pack->as<AstTypePackExplicit>())
return visit(p);
else if (auto p = pack->as<AstTypePackVariadic>())
return visit(p);
else if (auto p = pack->as<AstTypePackGeneric>())
return visit(p);
}
void visit(AstTypePackExplicit* tp)
{
for (AstType* type : tp->typeList.types)
visit(type);
if (tp->typeList.tailType)
visit(tp->typeList.tailType);
}
void visit(AstTypePackVariadic* tp)
{
visit(tp->variadicType);
}
void visit(AstTypePackGeneric* tp)
{
Scope* scope = findInnermostScope(tp->location);
LUAU_ASSERT(scope);
std::optional<TypePackId> alias = scope->lookupPack(tp->genericName.value);
if (!alias.has_value())
{
if (scope->lookupType(tp->genericName.value))
{
reportError(
SwappedGenericTypeParameter{
tp->genericName.value,
SwappedGenericTypeParameter::Kind::Pack,
},
tp->location
);
}
}
else
{
reportError(UnknownSymbol{tp->genericName.value, UnknownSymbol::Context::Type}, tp->location);
}
}
void visitGenerics(AstArray<AstGenericType*> generics, AstArray<AstGenericTypePack*> genericPacks)
{
DenseHashSet<AstName> seen{AstName{}};
for (const auto* g : generics)
{
if (seen.contains(g->name))
reportError(DuplicateGenericParameter{g->name.value}, g->location);
else
seen.insert(g->name);
if (g->defaultValue)
visit(g->defaultValue);
}
for (const auto* g : genericPacks)
{
if (seen.contains(g->name))
reportError(DuplicateGenericParameter{g->name.value}, g->location);
else
seen.insert(g->name);
if (g->defaultValue)
visit(g->defaultValue);
}
}
Scope* findInnermostScope(Location location) const
{
Scope* bestScope = module->getModuleScope().get();
bool didNarrow;
do
{
didNarrow = false;
for (auto scope : bestScope->children)
{
if (scope->location.encloses(location))
{
bestScope = scope.get();
didNarrow = true;
break;
}
}
} while (didNarrow && bestScope->children.size() > 0);
return bestScope;
}
std::optional<TypePackId> lookupPackAnnotation(AstTypePack* annotation) const
{
TypePackId* tp = module->astResolvedTypePacks.find(annotation);
if (tp != nullptr)
return {follow(*tp)};
return {};
}
void reportError(TypeErrorData data, const Location& location) void reportError(TypeErrorData data, const Location& location)
{ {
module->errors.emplace_back(location, module->name, std::move(data)); module->errors.emplace_back(location, module->name, std::move(data));

View file

@ -17,15 +17,11 @@
LUAU_FASTFLAGVARIABLE(DebugLuauCheckNormalizeInvariant) LUAU_FASTFLAGVARIABLE(DebugLuauCheckNormalizeInvariant)
LUAU_FASTFLAGVARIABLE(LuauNormalizeNegatedErrorToAnError)
LUAU_FASTFLAGVARIABLE(LuauNormalizeIntersectErrorToAnError)
LUAU_FASTINTVARIABLE(LuauNormalizeCacheLimit, 100000) LUAU_FASTINTVARIABLE(LuauNormalizeCacheLimit, 100000)
LUAU_FASTINTVARIABLE(LuauNormalizeIntersectionLimit, 200) LUAU_FASTINTVARIABLE(LuauNormalizeIntersectionLimit, 200)
LUAU_FASTINTVARIABLE(LuauNormalizeUnionLimit, 100) LUAU_FASTINTVARIABLE(LuauNormalizeUnionLimit, 100)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAGVARIABLE(LuauFixInfiniteRecursionInNormalization) LUAU_FASTFLAGVARIABLE(LuauFixInfiniteRecursionInNormalization)
LUAU_FASTFLAGVARIABLE(LuauNormalizedBufferIsNotUnknown)
LUAU_FASTFLAGVARIABLE(LuauNormalizeLimitFunctionSet)
LUAU_FASTFLAGVARIABLE(LuauNormalizationCatchMetatableCycles) LUAU_FASTFLAGVARIABLE(LuauNormalizationCatchMetatableCycles)
namespace Luau namespace Luau
@ -253,23 +249,23 @@ bool isSubtype(const NormalizedStringType& subStr, const NormalizedStringType& s
return true; 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) if (result.second)
ordering.push_back(ty); ordering.push_back(ty);
LUAU_ASSERT(ordering.size() == classes.size()); LUAU_ASSERT(ordering.size() == externTypes.size());
} }
void NormalizedClassType::resetToNever() void NormalizedExternType::resetToNever()
{ {
ordering.clear(); ordering.clear();
classes.clear(); externTypes.clear();
} }
bool NormalizedClassType::isNever() const bool NormalizedExternType::isNever() const
{ {
return classes.empty(); return externTypes.empty();
} }
void NormalizedFunctionType::resetToTop() void NormalizedFunctionType::resetToTop()
@ -308,19 +304,17 @@ bool NormalizedType::isUnknown() const
// Otherwise, we can still be unknown! // Otherwise, we can still be unknown!
bool hasAllPrimitives = isPrim(booleans, PrimitiveType::Boolean) && isPrim(nils, PrimitiveType::NilType) && isNumber(numbers) && bool hasAllPrimitives = isPrim(booleans, PrimitiveType::Boolean) && isPrim(nils, PrimitiveType::NilType) && isNumber(numbers) &&
strings.isString() && strings.isString() && isThread(threads) && isBuffer(buffers);
(FFlag::LuauNormalizedBufferIsNotUnknown ? isThread(threads) && isBuffer(buffers)
: isPrim(threads, PrimitiveType::Thread) && isThread(threads));
// Check is class // Check is class
bool isTopClass = false; bool isTopExternType = false;
for (auto [t, disj] : classes.classes) 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()) if (ct->name == "class" && disj.empty())
{ {
isTopClass = true; isTopExternType = true;
break; break;
} }
} }
@ -336,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 // 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 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(); !hasBuffers() && !hasTables() && !hasFunctions() && !hasTyvars();
} }
bool NormalizedType::isSubtypeOfString() const 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(); !hasBuffers() && !hasTables() && !hasFunctions() && !hasTyvars();
} }
bool NormalizedType::isSubtypeOfBooleans() const 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(); !hasBuffers() && !hasTables() && !hasFunctions() && !hasTyvars();
} }
@ -386,9 +380,9 @@ bool NormalizedType::hasBooleans() const
return !get<NeverType>(booleans); return !get<NeverType>(booleans);
} }
bool NormalizedType::hasClasses() const bool NormalizedType::hasExternTypes() const
{ {
return !classes.isNever(); return !externTypes.isNever();
} }
bool NormalizedType::hasErrors() const bool NormalizedType::hasErrors() const
@ -446,7 +440,7 @@ bool NormalizedType::isFalsy() const
hasAFalse = !bs->value; 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()); !hasBuffers() && !hasTables() && !hasFunctions() && !hasTyvars());
} }
@ -458,7 +452,7 @@ bool NormalizedType::isTruthy() const
static bool isShallowInhabited(const NormalizedType& norm) static bool isShallowInhabited(const NormalizedType& norm)
{ {
// This test is just a shallow check, for example it returns `true` for `{ p : never }` // 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.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(); !get<NeverType>(norm.buffers) || !norm.functions.isNever() || !norm.tables.empty() || !norm.tyvars.empty();
} }
@ -477,7 +471,7 @@ NormalizationResult Normalizer::isInhabited(const NormalizedType* norm, Set<Type
return NormalizationResult::HitLimits; return NormalizationResult::HitLimits;
if (!get<NeverType>(norm->tops) || !get<NeverType>(norm->booleans) || !get<NeverType>(norm->errors) || !get<NeverType>(norm->nils) || 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()) !norm->strings.isNever() || !norm->functions.isNever())
return NormalizationResult::True; return NormalizationResult::True;
@ -625,13 +619,13 @@ static int tyvarIndex(TypeId ty)
return 0; 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; return false;
auto first = classes.classes.begin(); auto first = externTypes.externTypes.begin();
if (first->first != builtinTypes->classType) if (first->first != builtinTypes->externType)
return false; return false;
if (!first->second.empty()) if (!first->second.empty())
@ -640,11 +634,11 @@ static bool isTop(NotNull<BuiltinTypes> builtinTypes, const NormalizedClassType&
return true; return true;
} }
static void resetToTop(NotNull<BuiltinTypes> builtinTypes, NormalizedClassType& classes) static void resetToTop(NotNull<BuiltinTypes> builtinTypes, NormalizedExternType& externTypes)
{ {
classes.ordering.clear(); externTypes.ordering.clear();
classes.classes.clear(); externTypes.externTypes.clear();
classes.pushPair(builtinTypes->classType, TypeIds{}); externTypes.pushPair(builtinTypes->externType, TypeIds{});
} }
#ifdef LUAU_ASSERTENABLED #ifdef LUAU_ASSERTENABLED
@ -768,50 +762,50 @@ static bool areNormalizedTables(const TypeIds& tys)
return true; 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); const ExternType* etv = get<ExternType>(ty);
if (!ctv) if (!etv)
{ {
return false; return false;
} }
for (TypeId negation : negations) for (TypeId negation : negations)
{ {
const ClassType* nctv = get<ClassType>(negation); const ExternType* nctv = get<ExternType>(negation);
if (!nctv) if (!nctv)
{ {
return false; return false;
} }
if (!isSubclass(nctv, ctv)) if (!isSubclass(nctv, etv))
{ {
return false; return false;
} }
} }
for (const auto& [otherTy, otherNegations] : tys.classes) for (const auto& [otherTy, otherNegations] : tys.externTypes)
{ {
if (otherTy == ty) if (otherTy == ty)
continue; continue;
const ClassType* octv = get<ClassType>(otherTy); const ExternType* octv = get<ExternType>(otherTy);
if (!octv) if (!octv)
{ {
return false; 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) if (!c)
return false; return false;
return isSubclass(ctv, c); return isSubclass(etv, c);
}; };
if (!std::any_of(otherNegations.begin(), otherNegations.end(), iss)) if (!std::any_of(otherNegations.begin(), otherNegations.end(), iss))
@ -853,7 +847,7 @@ static void assertInvariant(const NormalizedType& norm)
LUAU_ASSERT(isNormalizedTop(norm.tops)); LUAU_ASSERT(isNormalizedTop(norm.tops));
LUAU_ASSERT(isNormalizedBoolean(norm.booleans)); LUAU_ASSERT(isNormalizedBoolean(norm.booleans));
LUAU_ASSERT(areNormalizedClasses(norm.classes)); LUAU_ASSERT(areNormalizedExternTypes(norm.externTypes));
LUAU_ASSERT(isNormalizedError(norm.errors)); LUAU_ASSERT(isNormalizedError(norm.errors));
LUAU_ASSERT(isNormalizedNil(norm.nils)); LUAU_ASSERT(isNormalizedNil(norm.nils));
LUAU_ASSERT(isNormalizedNumber(norm.numbers)); LUAU_ASSERT(isNormalizedNumber(norm.numbers));
@ -994,7 +988,7 @@ void Normalizer::clearNormal(NormalizedType& norm)
{ {
norm.tops = builtinTypes->neverType; norm.tops = builtinTypes->neverType;
norm.booleans = builtinTypes->neverType; norm.booleans = builtinTypes->neverType;
norm.classes.resetToNever(); norm.externTypes.resetToNever();
norm.errors = builtinTypes->neverType; norm.errors = builtinTypes->neverType;
norm.nils = builtinTypes->neverType; norm.nils = builtinTypes->neverType;
norm.numbers = builtinTypes->neverType; norm.numbers = builtinTypes->neverType;
@ -1144,17 +1138,17 @@ TypeId Normalizer::unionOfBools(TypeId here, TypeId there)
return builtinTypes->booleanType; return builtinTypes->booleanType;
} }
void Normalizer::unionClassesWithClass(TypeIds& heres, TypeId there) void Normalizer::unionExternTypesWithExternType(TypeIds& heres, TypeId there)
{ {
if (heres.count(there)) if (heres.count(there))
return; return;
const ClassType* tctv = get<ClassType>(there); const ExternType* tctv = get<ExternType>(there);
for (auto it = heres.begin(); it != heres.end();) for (auto it = heres.begin(); it != heres.end();)
{ {
TypeId here = *it; TypeId here = *it;
const ClassType* hctv = get<ClassType>(here); const ExternType* hctv = get<ExternType>(here);
if (isSubclass(tctv, hctv)) if (isSubclass(tctv, hctv))
return; return;
else if (isSubclass(hctv, tctv)) else if (isSubclass(hctv, tctv))
@ -1166,16 +1160,16 @@ void Normalizer::unionClassesWithClass(TypeIds& heres, TypeId there)
heres.insert(there); heres.insert(there);
} }
void Normalizer::unionClasses(TypeIds& heres, const TypeIds& theres) void Normalizer::unionExternTypes(TypeIds& heres, const TypeIds& theres)
{ {
for (TypeId there : theres) for (TypeId there : theres)
unionClassesWithClass(heres, there); unionExternTypesWithExternType(heres, there);
} }
static bool isSubclass(TypeId test, TypeId parent) static bool isSubclass(TypeId test, TypeId parent)
{ {
const ClassType* testCtv = get<ClassType>(test); const ExternType* testCtv = get<ExternType>(test);
const ClassType* parentCtv = get<ClassType>(parent); const ExternType* parentCtv = get<ExternType>(parent);
LUAU_ASSERT(testCtv); LUAU_ASSERT(testCtv);
LUAU_ASSERT(parentCtv); LUAU_ASSERT(parentCtv);
@ -1183,12 +1177,12 @@ static bool isSubclass(TypeId test, TypeId parent)
return isSubclass(testCtv, parentCtv); 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();) for (auto it = heres.ordering.begin(); it != heres.ordering.end();)
{ {
TypeId hereTy = *it; 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 // 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 // must ensure that it is negated by one of the negations in the same
@ -1210,7 +1204,7 @@ void Normalizer::unionClassesWithClass(NormalizedClassType& heres, TypeId there)
} }
// If the incoming class is a superclass of one of the // If the incoming class is a superclass of one of the
// negations, then the negation no longer applies and must be // 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 // are, at this time, entirely persistent (we do not clone
// them), a pointer identity check is sufficient. // them), a pointer identity check is sufficient.
else if (isSubclass(hereNegation, there)) else if (isSubclass(hereNegation, there))
@ -1237,7 +1231,7 @@ void Normalizer::unionClassesWithClass(NormalizedClassType& heres, TypeId there)
{ {
TypeIds negations = std::move(hereNegations); TypeIds negations = std::move(hereNegations);
it = heres.ordering.erase(it); it = heres.ordering.erase(it);
heres.classes.erase(hereTy); heres.externTypes.erase(hereTy);
heres.pushPair(there, std::move(negations)); heres.pushPair(there, std::move(negations));
return; return;
@ -1254,10 +1248,10 @@ void Normalizer::unionClassesWithClass(NormalizedClassType& heres, TypeId there)
heres.pushPair(there, TypeIds{}); 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 // This method bears much similarity with unionExternTypesWithExternType, but is
// solving a more general problem. In unionClassesWithClass, we are dealing // solving a more general problem. In unionExternTypesWithExternType, we are dealing
// with a singular positive type. Since it's one type, we can use early // 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 // 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 // have negations to worry about combining. The two aspects combine to make
@ -1266,9 +1260,9 @@ void Normalizer::unionClasses(NormalizedClassType& heres, const NormalizedClassT
for (const TypeId thereTy : theres.ordering) 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 // incoming class is completely unrelated to any class in the current
// map, we must insert the incoming pair as-is. // map, we must insert the incoming pair as-is.
bool insert = true; bool insert = true;
@ -1276,7 +1270,7 @@ void Normalizer::unionClasses(NormalizedClassType& heres, const NormalizedClassT
for (auto it = heres.ordering.begin(); it != heres.ordering.end();) for (auto it = heres.ordering.begin(); it != heres.ordering.end();)
{ {
TypeId hereTy = *it; TypeId hereTy = *it;
TypeIds& hereNegations = heres.classes.at(hereTy); TypeIds& hereNegations = heres.externTypes.at(hereTy);
if (isSubclass(thereTy, hereTy)) if (isSubclass(thereTy, hereTy))
{ {
@ -1300,7 +1294,7 @@ void Normalizer::unionClasses(NormalizedClassType& heres, const NormalizedClassT
// If the incoming class is a superclass of one of the // If the incoming class is a superclass of one of the
// negations, then the negation no longer applies and must // negations, then the negation no longer applies and must
// be removed. This is also true if they are equal. Since // 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. // clone them), a pointer identity check is sufficient.
else if (isSubclass(hereNegateTy, thereTy)) else if (isSubclass(hereNegateTy, thereTy))
{ {
@ -1325,17 +1319,17 @@ void Normalizer::unionClasses(NormalizedClassType& heres, const NormalizedClassT
else if (isSubclass(hereTy, thereTy)) else if (isSubclass(hereTy, thereTy))
{ {
TypeIds negations = std::move(hereNegations); TypeIds negations = std::move(hereNegations);
unionClasses(negations, thereNegations); unionExternTypes(negations, thereNegations);
it = heres.ordering.erase(it); it = heres.ordering.erase(it);
heres.classes.erase(hereTy); heres.externTypes.erase(hereTy);
heres.pushPair(thereTy, std::move(negations)); heres.pushPair(thereTy, std::move(negations));
insert = false; insert = false;
break; break;
} }
else if (hereTy == thereTy) else if (hereTy == thereTy)
{ {
unionClasses(hereNegations, thereNegations); unionExternTypes(hereNegations, thereNegations);
insert = false; insert = false;
break; break;
} }
@ -1691,15 +1685,12 @@ NormalizationResult Normalizer::unionNormals(NormalizedType& here, const Normali
return res; return res;
} }
if (FFlag::LuauNormalizeLimitFunctionSet)
{
// Limit based on worst-case expansion of the function unions // Limit based on worst-case expansion of the function unions
if (here.functions.parts.size() * there.functions.parts.size() >= size_t(FInt::LuauNormalizeUnionLimit)) if (here.functions.parts.size() * there.functions.parts.size() >= size_t(FInt::LuauNormalizeUnionLimit))
return NormalizationResult::HitLimits; return NormalizationResult::HitLimits;
}
here.booleans = unionOfBools(here.booleans, there.booleans); 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.errors = (get<NeverType>(there.errors) ? here.errors : there.errors);
here.nils = (get<NeverType>(there.nils) ? here.nils : there.nils); here.nils = (get<NeverType>(there.nils) ? here.nils : there.nils);
@ -1839,8 +1830,8 @@ NormalizationResult Normalizer::unionNormalWithTy(
unionFunctionsWithFunction(here.functions, there); unionFunctionsWithFunction(here.functions, there);
else if (get<TableType>(there) || get<MetatableType>(there)) else if (get<TableType>(there) || get<MetatableType>(there))
unionTablesWithTable(here.tables, there); unionTablesWithTable(here.tables, there);
else if (get<ClassType>(there)) else if (get<ExternType>(there))
unionClassesWithClass(here.classes, there); unionExternTypesWithExternType(here.externTypes, there);
else if (get<ErrorType>(there)) else if (get<ErrorType>(there))
here.errors = there; here.errors = there;
else if (const PrimitiveType* ptv = get<PrimitiveType>(there)) else if (const PrimitiveType* ptv = get<PrimitiveType>(there))
@ -1953,29 +1944,29 @@ std::optional<NormalizedType> Normalizer::negateNormal(const NormalizedType& her
result.booleans = builtinTypes->trueType; 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 else
{ {
TypeIds rootNegations{}; 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); rootNegations.insert(hereParent);
for (TypeId hereNegation : hereNegations) for (TypeId hereNegation : hereNegations)
unionClassesWithClass(result.classes, hereNegation); unionExternTypesWithExternType(result.externTypes, hereNegation);
} }
if (!rootNegations.empty()) 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; result.nils = get<NeverType>(here.nils) ? builtinTypes->nilType : builtinTypes->neverType;
@ -2153,7 +2144,7 @@ TypeId Normalizer::intersectionOfBools(TypeId here, TypeId there)
return there; return there;
} }
void Normalizer::intersectClasses(NormalizedClassType& heres, const NormalizedClassType& theres) void Normalizer::intersectExternTypes(NormalizedExternType& heres, const NormalizedExternType& theres)
{ {
if (theres.isNever()) if (theres.isNever())
{ {
@ -2187,12 +2178,12 @@ void Normalizer::intersectClasses(NormalizedClassType& heres, const NormalizedCl
// declare the result of the intersection operation to be never. // declare the result of the intersection operation to be never.
for (const TypeId thereTy : theres.ordering) 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();) for (auto it = heres.ordering.begin(); it != heres.ordering.end();)
{ {
TypeId hereTy = *it; TypeId hereTy = *it;
TypeIds& hereNegations = heres.classes.at(hereTy); TypeIds& hereNegations = heres.externTypes.at(hereTy);
if (isSubclass(thereTy, hereTy)) if (isSubclass(thereTy, hereTy))
{ {
@ -2215,10 +2206,10 @@ void Normalizer::intersectClasses(NormalizedClassType& heres, const NormalizedCl
} }
} }
unionClasses(negations, thereNegations); unionExternTypes(negations, thereNegations);
it = heres.ordering.erase(it); it = heres.ordering.erase(it);
heres.classes.erase(hereTy); heres.externTypes.erase(hereTy);
heres.pushPair(thereTy, std::move(negations)); heres.pushPair(thereTy, std::move(negations));
break; break;
} }
@ -2243,15 +2234,15 @@ void Normalizer::intersectClasses(NormalizedClassType& heres, const NormalizedCl
{ {
if (isSubclass(hereTy, *nIt)) if (isSubclass(hereTy, *nIt))
{ {
// eg SomeClass & (class & ~SomeClass) // eg SomeExternType & (class & ~SomeExternType)
// or SomeClass & (class & ~ParentClass) // or SomeExternType & (class & ~ParentExternType)
heres.classes.erase(hereTy); heres.externTypes.erase(hereTy);
it = heres.ordering.erase(it); it = heres.ordering.erase(it);
erasedHere = true; erasedHere = true;
break; break;
} }
// eg SomeClass & (class & ~Unrelated) // eg SomeExternType & (class & ~Unrelated)
if (!isSubclass(*nIt, hereTy)) if (!isSubclass(*nIt, hereTy))
nIt = negations.erase(nIt); nIt = negations.erase(nIt);
else else
@ -2260,30 +2251,30 @@ void Normalizer::intersectClasses(NormalizedClassType& heres, const NormalizedCl
if (!erasedHere) if (!erasedHere)
{ {
unionClasses(hereNegations, negations); unionExternTypes(hereNegations, negations);
++it; ++it;
} }
} }
else if (hereTy == thereTy) else if (hereTy == thereTy)
{ {
unionClasses(hereNegations, thereNegations); unionExternTypes(hereNegations, thereNegations);
break; break;
} }
else else
{ {
it = heres.ordering.erase(it); 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();) for (auto it = heres.ordering.begin(); it != heres.ordering.end();)
{ {
TypeId hereTy = *it; 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 // If the incoming class _is_ the current class, we skip it. Maybe
// another entry will have a different story. We check for this first // another entry will have a different story. We check for this first
@ -2328,7 +2319,7 @@ void Normalizer::intersectClassesWithClass(NormalizedClassType& heres, TypeId th
} }
it = heres.ordering.erase(it); it = heres.ordering.erase(it);
heres.classes.erase(hereTy); heres.externTypes.erase(hereTy);
if (!emptyIntersectWithNegation) if (!emptyIntersectWithNegation)
heres.pushPair(there, std::move(negations)); heres.pushPair(there, std::move(negations));
break; break;
@ -2344,7 +2335,7 @@ void Normalizer::intersectClassesWithClass(NormalizedClassType& heres, TypeId th
else else
{ {
it = heres.ordering.erase(it); it = heres.ordering.erase(it);
heres.classes.erase(hereTy); heres.externTypes.erase(hereTy);
} }
} }
} }
@ -3087,15 +3078,12 @@ NormalizationResult Normalizer::intersectNormals(NormalizedType& here, const Nor
if (here.tables.size() * there.tables.size() >= size_t(FInt::LuauNormalizeIntersectionLimit)) if (here.tables.size() * there.tables.size() >= size_t(FInt::LuauNormalizeIntersectionLimit))
return NormalizationResult::HitLimits; return NormalizationResult::HitLimits;
if (FFlag::LuauNormalizeLimitFunctionSet)
{
if (here.functions.parts.size() * there.functions.parts.size() >= size_t(FInt::LuauNormalizeIntersectionLimit)) if (here.functions.parts.size() * there.functions.parts.size() >= size_t(FInt::LuauNormalizeIntersectionLimit))
return NormalizationResult::HitLimits; return NormalizationResult::HitLimits;
}
here.booleans = intersectionOfBools(here.booleans, there.booleans); 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.errors = (get<NeverType>(there.errors) ? there.errors : here.errors);
here.nils = (get<NeverType>(there.nils) ? there.nils : here.nils); here.nils = (get<NeverType>(there.nils) ? there.nils : here.nils);
here.numbers = (get<NeverType>(there.numbers) ? there.numbers : here.numbers); here.numbers = (get<NeverType>(there.numbers) ? there.numbers : here.numbers);
@ -3217,18 +3205,18 @@ NormalizationResult Normalizer::intersectNormalWithTy(
intersectTablesWithTable(tables, there, seenTablePropPairs, seenSetTypes); intersectTablesWithTable(tables, there, seenTablePropPairs, seenSetTypes);
here.tables = std::move(tables); 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); clearNormal(here);
intersectClassesWithClass(nct, there); intersectExternTypesWithExternType(nct, there);
here.classes = std::move(nct); here.externTypes = std::move(nct);
} }
else if (get<ErrorType>(there)) else if (get<ErrorType>(there))
{ {
TypeId errors = here.errors; TypeId errors = here.errors;
clearNormal(here); clearNormal(here);
here.errors = FFlag::LuauNormalizeIntersectErrorToAnError && get<ErrorType>(errors) ? errors : there; here.errors = get<ErrorType>(errors) ? errors : there;
} }
else if (const PrimitiveType* ptv = get<PrimitiveType>(there)) else if (const PrimitiveType* ptv = get<PrimitiveType>(there))
{ {
@ -3286,7 +3274,7 @@ NormalizationResult Normalizer::intersectNormalWithTy(
subtractPrimitive(here, ntv->ty); subtractPrimitive(here, ntv->ty);
else if (const SingletonType* stv = get<SingletonType>(t)) else if (const SingletonType* stv = get<SingletonType>(t))
subtractSingleton(here, follow(ntv->ty)); subtractSingleton(here, follow(ntv->ty));
else if (get<ClassType>(t)) else if (get<ExternType>(t))
{ {
NormalizationResult res = intersectNormalWithNegationTy(t, here); NormalizationResult res = intersectNormalWithNegationTy(t, here);
if (shouldEarlyExit(res)) if (shouldEarlyExit(res))
@ -3325,12 +3313,12 @@ NormalizationResult Normalizer::intersectNormalWithTy(
clearNormal(here); clearNormal(here);
return NormalizationResult::True; return NormalizationResult::True;
} }
else if (FFlag::LuauNormalizeNegatedErrorToAnError && get<ErrorType>(t)) else if (get<ErrorType>(t))
{ {
// ~error is still an error, so intersecting with the negation is the same as intersecting with a type // ~error is still an error, so intersecting with the negation is the same as intersecting with a type
TypeId errors = here.errors; TypeId errors = here.errors;
clearNormal(here); clearNormal(here);
here.errors = FFlag::LuauNormalizeIntersectErrorToAnError && get<ErrorType>(errors) ? errors : t; here.errors = get<ErrorType>(errors) ? errors : t;
} }
else if (auto nt = get<NegationType>(t)) else if (auto nt = get<NegationType>(t))
{ {
@ -3346,7 +3334,7 @@ NormalizationResult Normalizer::intersectNormalWithTy(
} }
else if (get<NeverType>(there)) else if (get<NeverType>(there))
{ {
here.classes.resetToNever(); here.externTypes.resetToNever();
} }
else if (get<NoRefineType>(there)) else if (get<NoRefineType>(there))
{ {
@ -3415,18 +3403,18 @@ TypeId Normalizer::typeFromNormal(const NormalizedType& norm)
if (!get<NeverType>(norm.booleans)) if (!get<NeverType>(norm.booleans))
result.push_back(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; 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()) if (normNegations.empty())
{ {

View file

@ -10,6 +10,8 @@
#include "Luau/TypeUtils.h" #include "Luau/TypeUtils.h"
#include "Luau/Unifier2.h" #include "Luau/Unifier2.h"
LUAU_FASTFLAGVARIABLE(LuauArityMismatchOnUndersaturatedUnknownArguments)
namespace Luau namespace Luau
{ {
@ -254,8 +256,24 @@ std::pair<OverloadResolver::Analysis, ErrorVec> OverloadResolver::checkOverload_
} }
// If any of the unsatisfied arguments are not supertypes of // If any of the unsatisfied arguments are not supertypes of
// nil, then this overload does not match. // nil or are `unknown`, then this overload does not match.
for (size_t i = firstUnsatisfiedArgument; i < requiredHead.size(); ++i) for (size_t i = firstUnsatisfiedArgument; i < requiredHead.size(); ++i)
{
if (FFlag::LuauArityMismatchOnUndersaturatedUnknownArguments)
{
if (get<UnknownType>(follow(requiredHead[i])) || !subtyping.isSubtype(builtinTypes->nilType, requiredHead[i], scope).isSubtype)
{
auto [minParams, optMaxParams] = getParameterExtents(TxnLog::empty(), fn->argTypes);
for (auto arg : fn->argTypes)
if (get<UnknownType>(follow(arg)))
minParams += 1;
TypeError error{fnExpr->location, CountMismatch{minParams, optMaxParams, args->head.size(), CountMismatch::Arg, isVariadic}};
return {Analysis::ArityMismatch, {error}};
}
}
else
{ {
if (!subtyping.isSubtype(builtinTypes->nilType, requiredHead[i], scope).isSubtype) if (!subtyping.isSubtype(builtinTypes->nilType, requiredHead[i], scope).isSubtype)
{ {
@ -265,6 +283,7 @@ std::pair<OverloadResolver::Analysis, ErrorVec> OverloadResolver::checkOverload_
return {Analysis::ArityMismatch, {error}}; return {Analysis::ArityMismatch, {error}};
} }
} }
}
return {Analysis::Ok, {}}; return {Analysis::Ok, {}};
} }

View file

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

View file

@ -6,6 +6,7 @@
#include "Luau/DenseHash.h" #include "Luau/DenseHash.h"
#include "Luau/RecursionCounter.h" #include "Luau/RecursionCounter.h"
#include "Luau/Set.h" #include "Luau/Set.h"
#include "Luau/Type.h"
#include "Luau/TypeArena.h" #include "Luau/TypeArena.h"
#include "Luau/TypePairHash.h" #include "Luau/TypePairHash.h"
#include "Luau/TypeUtils.h" #include "Luau/TypeUtils.h"
@ -14,8 +15,10 @@
LUAU_FASTINT(LuauTypeReductionRecursionLimit) LUAU_FASTINT(LuauTypeReductionRecursionLimit)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_DYNAMIC_FASTINTVARIABLE(LuauSimplificationComplexityLimit, 8); LUAU_DYNAMIC_FASTINTVARIABLE(LuauSimplificationComplexityLimit, 8)
LUAU_FASTFLAGVARIABLE(LuauFlagBasicIntersectFollows); LUAU_FASTFLAGVARIABLE(LuauSimplificationRecheckAssumption)
LUAU_FASTFLAGVARIABLE(LuauOptimizeFalsyAndTruthyIntersect)
LUAU_FASTFLAGVARIABLE(LuauSimplificationTableExternType)
namespace Luau namespace Luau
{ {
@ -47,6 +50,8 @@ struct TypeSimplifier
// Attempt to intersect the two types. Does not recurse. Does not handle // Attempt to intersect the two types. Does not recurse. Does not handle
// unions, intersections, or negations. // unions, intersections, or negations.
std::optional<TypeId> basicIntersect(TypeId left, TypeId right); std::optional<TypeId> basicIntersect(TypeId left, TypeId right);
std::optional<TypeId> basicIntersectWithTruthy(TypeId target) const;
std::optional<TypeId> basicIntersectWithFalsy(TypeId target) const;
TypeId intersect(TypeId left, TypeId right); TypeId intersect(TypeId left, TypeId right);
TypeId union_(TypeId left, TypeId right); TypeId union_(TypeId left, TypeId right);
@ -313,11 +318,13 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
{ {
if (get<AnyType>(right)) if (get<AnyType>(right))
return Relation::Subset; return Relation::Subset;
else if (get<UnknownType>(right))
if (get<UnknownType>(right))
return Relation::Coincident; return Relation::Coincident;
else if (get<ErrorType>(right))
if (get<ErrorType>(right))
return Relation::Disjoint; return Relation::Disjoint;
else
return Relation::Superset; return Relation::Superset;
} }
@ -328,7 +335,7 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
{ {
if (get<AnyType>(right)) if (get<AnyType>(right))
return Relation::Coincident; return Relation::Coincident;
else
return Relation::Superset; return Relation::Superset;
} }
@ -353,7 +360,7 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
// * FunctionType // * FunctionType
// * TableType // * TableType
// * MetatableType // * MetatableType
// * ClassType // * ExternType
// * UnionType // * UnionType
// * IntersectionType // * IntersectionType
// * NegationType // * NegationType
@ -361,26 +368,33 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
if (isTypeVariable(left) || isTypeVariable(right)) if (isTypeVariable(left) || isTypeVariable(right))
return Relation::Intersects; 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>(left))
{ {
if (get<ErrorType>(right)) if (get<ErrorType>(right))
return Relation::Coincident; return Relation::Coincident;
else if (get<AnyType>(right)) else if (get<AnyType>(right))
return Relation::Subset; 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)); return flip(relate(right, left, seen));
if (get<NeverType>(left)) if (get<NeverType>(left))
{ {
if (get<NeverType>(right)) if (get<NeverType>(right))
return Relation::Coincident; 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)); return flip(relate(right, left, seen));
if (auto ut = get<IntersectionType>(left)) if (auto ut = get<IntersectionType>(left))
@ -444,7 +458,7 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
{ {
if (lp->type == rp->type) if (lp->type == rp->type)
return Relation::Coincident; return Relation::Coincident;
else
return Relation::Disjoint; return Relation::Disjoint;
} }
@ -452,9 +466,10 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
{ {
if (lp->type == PrimitiveType::String && rs->variant.get_if<StringSingleton>()) if (lp->type == PrimitiveType::String && rs->variant.get_if<StringSingleton>())
return Relation::Superset; 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; return Relation::Superset;
else
return Relation::Disjoint; return Relation::Disjoint;
} }
@ -462,33 +477,34 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
{ {
if (get<FunctionType>(right)) if (get<FunctionType>(right))
return Relation::Superset; return Relation::Superset;
else
return Relation::Disjoint; return Relation::Disjoint;
} }
if (lp->type == PrimitiveType::Table) if (lp->type == PrimitiveType::Table)
{ {
if (get<TableType>(right)) if (get<TableType>(right))
return Relation::Superset; 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; return Relation::Disjoint;
} }
if (auto ls = get<SingletonType>(left)) 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; return Relation::Disjoint;
if (get<PrimitiveType>(right)) if (get<PrimitiveType>(right))
return flip(relate(right, left, seen)); return flip(relate(right, left, seen));
if (auto rs = get<SingletonType>(right)) if (auto rs = get<SingletonType>(right))
{ {
if (ls->variant == rs->variant) if (ls->variant == rs->variant)
return Relation::Coincident; return Relation::Coincident;
else
return Relation::Disjoint; return Relation::Disjoint;
} }
} }
@ -499,10 +515,10 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
{ {
if (rp->type == PrimitiveType::Function) if (rp->type == PrimitiveType::Function)
return Relation::Subset; return Relation::Subset;
else
return Relation::Disjoint; return Relation::Disjoint;
} }
else
return Relation::Intersects; return Relation::Intersects;
} }
@ -512,10 +528,11 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
{ {
if (rp->type == PrimitiveType::Table) if (rp->type == PrimitiveType::Table)
return Relation::Subset; 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. // TODO PROBABLY indexers and metatables.
if (1 == rt->props.size()) if (1 == rt->props.size())
@ -535,28 +552,57 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
*/ */
if (lt->props.size() > 1 && r == Relation::Superset) if (lt->props.size() > 1 && r == Relation::Superset)
return Relation::Intersects; 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)); 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 // TODO metatables
return Relation::Disjoint; 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)) if (isSubclass(ct, rct))
return Relation::Subset; return Relation::Subset;
else if (isSubclass(rct, ct))
if (isSubclass(rct, ct))
return Relation::Superset; return Relation::Superset;
else
return Relation::Disjoint; return Relation::Disjoint;
} }
@ -707,7 +753,9 @@ TypeId TypeSimplifier::intersectUnionWithType(TypeId left, TypeId right)
bool changed = false; bool changed = false;
std::set<TypeId> newParts; std::set<TypeId> newParts;
if (leftUnion->options.size() > (size_t)DFInt::LuauSimplificationComplexityLimit) size_t maxSize = DFInt::LuauSimplificationComplexityLimit;
if (leftUnion->options.size() > maxSize)
return arena->addType(IntersectionType{{left, right}}); return arena->addType(IntersectionType{{left, right}});
for (TypeId part : leftUnion) for (TypeId part : leftUnion)
@ -722,6 +770,13 @@ TypeId TypeSimplifier::intersectUnionWithType(TypeId left, TypeId right)
} }
newParts.insert(simplified); newParts.insert(simplified);
if (FFlag::LuauSimplificationRecheckAssumption)
{
// Initial combination size check could not predict nested union iteration
if (newParts.size() > maxSize)
return arena->addType(IntersectionType{{left, right}});
}
} }
if (!changed) if (!changed)
@ -762,6 +817,13 @@ TypeId TypeSimplifier::intersectUnions(TypeId left, TypeId right)
continue; continue;
newParts.insert(simplified); newParts.insert(simplified);
if (FFlag::LuauSimplificationRecheckAssumption)
{
// Initial combination size check could not predict nested union iteration
if (newParts.size() > maxSize)
return arena->addType(IntersectionType{{left, right}});
}
} }
} }
@ -840,6 +902,78 @@ TypeId TypeSimplifier::intersectNegatedUnion(TypeId left, TypeId right)
return intersectFromParts(std::move(newParts)); return intersectFromParts(std::move(newParts));
} }
std::optional<TypeId> TypeSimplifier::basicIntersectWithTruthy(TypeId target) const
{
target = follow(target);
if (is<UnknownType>(target))
return builtinTypes->truthyType;
if (is<AnyType>(target))
// any = *error-type* | unknown, so truthy & any = *error-type* | truthy
return arena->addType(UnionType{{builtinTypes->truthyType, builtinTypes->errorType}});
if (is<NeverType, ErrorType>(target))
return target;
if (is<FunctionType, TableType, MetatableType, ExternType>(target))
return target;
if (auto pt = get<PrimitiveType>(target))
{
switch (pt->type)
{
case PrimitiveType::NilType:
return builtinTypes->neverType;
case PrimitiveType::Boolean:
return builtinTypes->trueType;
default:
return target;
}
}
if (auto st = get<SingletonType>(target))
return st->variant == BooleanSingleton{false} ? builtinTypes->neverType : target;
return std::nullopt;
}
std::optional<TypeId> TypeSimplifier::basicIntersectWithFalsy(TypeId target) const
{
target = follow(target);
if (is<NeverType, ErrorType>(target))
return target;
if (is<AnyType>(target))
// any = *error-type* | unknown, so falsy & any = *error-type* | falsy
return arena->addType(UnionType{{builtinTypes->falsyType, builtinTypes->errorType}});
if (is<UnknownType>(target))
return builtinTypes->falsyType;
if (is<FunctionType, TableType, MetatableType, ExternType>(target))
return builtinTypes->neverType;
if (auto pt = get<PrimitiveType>(target))
{
switch (pt->type)
{
case PrimitiveType::NilType:
return builtinTypes->nilType;
case PrimitiveType::Boolean:
return builtinTypes->falseType;
default:
return builtinTypes->neverType;
}
}
if (auto st = get<SingletonType>(target))
return st->variant == BooleanSingleton{false} ? builtinTypes->falseType : builtinTypes->neverType;
return std::nullopt;
}
TypeId TypeSimplifier::intersectTypeWithNegation(TypeId left, TypeId right) TypeId TypeSimplifier::intersectTypeWithNegation(TypeId left, TypeId right)
{ {
const NegationType* leftNegation = get<NegationType>(left); const NegationType* leftNegation = get<NegationType>(left);
@ -1065,12 +1199,9 @@ TypeId TypeSimplifier::intersectIntersectionWithType(TypeId left, TypeId right)
} }
std::optional<TypeId> TypeSimplifier::basicIntersect(TypeId left, TypeId right) std::optional<TypeId> TypeSimplifier::basicIntersect(TypeId left, TypeId right)
{
if (FFlag::LuauFlagBasicIntersectFollows)
{ {
left = follow(left); left = follow(left);
right = follow(right); right = follow(right);
}
if (get<AnyType>(left) && get<ErrorType>(right)) if (get<AnyType>(left) && get<ErrorType>(right))
return right; return right;
@ -1179,6 +1310,25 @@ std::optional<TypeId> TypeSimplifier::basicIntersect(TypeId left, TypeId right)
return std::nullopt; return std::nullopt;
} }
if (FFlag::LuauOptimizeFalsyAndTruthyIntersect)
{
if (isTruthyType(left))
if (auto res = basicIntersectWithTruthy(right))
return res;
if (isTruthyType(right))
if (auto res = basicIntersectWithTruthy(left))
return res;
if (isFalsyType(left))
if (auto res = basicIntersectWithFalsy(right))
return res;
if (isFalsyType(right))
if (auto res = basicIntersectWithFalsy(left))
return res;
}
Relation relation = relate(left, right); Relation relation = relate(left, right);
if (left == right || Relation::Coincident == relation) if (left == right || Relation::Coincident == relation)
return left; return left;

View file

@ -2,12 +2,10 @@
#include "Luau/Substitution.h" #include "Luau/Substitution.h"
#include "Luau/Common.h" #include "Luau/Common.h"
#include "Luau/Clone.h"
#include "Luau/TxnLog.h" #include "Luau/TxnLog.h"
#include "Luau/Type.h" #include "Luau/Type.h"
#include <algorithm> #include <algorithm>
#include <stdexcept>
LUAU_FASTINTVARIABLE(LuauTarjanChildLimit, 10000) LUAU_FASTINTVARIABLE(LuauTarjanChildLimit, 10000)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
@ -18,9 +16,9 @@ LUAU_FASTFLAG(LuauDeprecatedAttribute)
namespace Luau namespace Luau
{ {
static TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log, bool alwaysClone) static TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log)
{ {
auto go = [ty, &dest, alwaysClone](auto&& a) auto go = [ty, &dest](auto&& a)
{ {
using T = std::decay_t<decltype(a)>; using T = std::decay_t<decltype(a)>;
@ -138,16 +136,11 @@ static TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log, bool a
clone.parts = a.parts; clone.parts = a.parts;
return dest.addType(std::move(clone)); return dest.addType(std::move(clone));
} }
else if constexpr (std::is_same_v<T, ClassType>) else if constexpr (std::is_same_v<T, ExternType>)
{ {
if (alwaysClone) ExternType clone{a.name, a.props, a.parent, a.metatable, a.tags, a.userData, a.definitionModuleName, a.definitionLocation, a.indexer};
{
ClassType 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)); return dest.addType(std::move(clone));
} }
else
return ty;
}
else if constexpr (std::is_same_v<T, NegationType>) else if constexpr (std::is_same_v<T, NegationType>)
return dest.addType(NegationType{a.ty}); return dest.addType(NegationType{a.ty});
else if constexpr (std::is_same_v<T, TypeFunctionInstanceType>) else if constexpr (std::is_same_v<T, TypeFunctionInstanceType>)
@ -259,21 +252,21 @@ void Tarjan::visitChildren(TypeId ty, int index)
for (TypePackId a : tfit->packArguments) for (TypePackId a : tfit->packArguments)
visitChild(a); 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()); visitChild(prop.type());
if (ctv->parent) if (etv->parent)
visitChild(*ctv->parent); visitChild(*etv->parent);
if (ctv->metatable) if (etv->metatable)
visitChild(*ctv->metatable); visitChild(*etv->metatable);
if (ctv->indexer) if (etv->indexer)
{ {
visitChild(ctv->indexer->indexType); visitChild(etv->indexer->indexType);
visitChild(ctv->indexer->indexResultType); visitChild(etv->indexer->indexResultType);
} }
} }
else if (const NegationType* ntv = get<NegationType>(ty)) else if (const NegationType* ntv = get<NegationType>(ty))
@ -547,6 +540,27 @@ void Tarjan::visitSCC(int index)
} }
} }
bool Tarjan::ignoreChildren(TypeId ty)
{
return false;
}
bool Tarjan::ignoreChildren(TypePackId ty)
{
return false;
}
// Some subclasses might ignore children visit, but not other actions like replacing the children
bool Tarjan::ignoreChildrenVisit(TypeId ty)
{
return ignoreChildren(ty);
}
bool Tarjan::ignoreChildrenVisit(TypePackId ty)
{
return ignoreChildren(ty);
}
TarjanResult Tarjan::findDirty(TypeId ty) TarjanResult Tarjan::findDirty(TypeId ty)
{ {
return visitRoot(ty); return visitRoot(ty);
@ -557,6 +571,11 @@ TarjanResult Tarjan::findDirty(TypePackId tp)
return visitRoot(tp); return visitRoot(tp);
} }
Substitution::Substitution(TypeArena* arena)
: Substitution(TxnLog::empty(), arena)
{
}
Substitution::Substitution(const TxnLog* log_, TypeArena* arena) Substitution::Substitution(const TxnLog* log_, TypeArena* arena)
: arena(arena) : arena(arena)
{ {
@ -657,7 +676,7 @@ void Substitution::resetState(const TxnLog* log, TypeArena* arena)
TypeId Substitution::clone(TypeId ty) TypeId Substitution::clone(TypeId ty)
{ {
return shallowClone(ty, *arena, log, /* alwaysClone */ true); return shallowClone(ty, *arena, log);
} }
TypePackId Substitution::clone(TypePackId tp) TypePackId Substitution::clone(TypePackId tp)
@ -819,21 +838,21 @@ void Substitution::replaceChildren(TypeId ty)
for (TypePackId& a : tfit->packArguments) for (TypePackId& a : tfit->packArguments)
a = replace(a); 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())); prop.setType(replace(prop.type()));
if (ctv->parent) if (etv->parent)
ctv->parent = replace(*ctv->parent); etv->parent = replace(*etv->parent);
if (ctv->metatable) if (etv->metatable)
ctv->metatable = replace(*ctv->metatable); etv->metatable = replace(*etv->metatable);
if (ctv->indexer) if (etv->indexer)
{ {
ctv->indexer->indexType = replace(ctv->indexer->indexType); etv->indexer->indexType = replace(etv->indexer->indexType);
ctv->indexer->indexResultType = replace(ctv->indexer->indexResultType); etv->indexer->indexResultType = replace(etv->indexer->indexResultType);
} }
} }
else if (NegationType* ntv = getMutable<NegationType>(ty)) else if (NegationType* ntv = getMutable<NegationType>(ty))
@ -873,4 +892,13 @@ void Substitution::replaceChildren(TypePackId tp)
} }
} }
template<typename Ty>
std::optional<Ty> Substitution::replace(std::optional<Ty> ty)
{
if (ty)
return replace(*ty);
else
return std::nullopt;
}
} // namespace Luau } // namespace Luau

View file

@ -7,13 +7,11 @@
#include "Luau/Normalize.h" #include "Luau/Normalize.h"
#include "Luau/RecursionCounter.h" #include "Luau/RecursionCounter.h"
#include "Luau/Scope.h" #include "Luau/Scope.h"
#include "Luau/StringUtils.h"
#include "Luau/Substitution.h" #include "Luau/Substitution.h"
#include "Luau/ToString.h" #include "Luau/ToString.h"
#include "Luau/TxnLog.h" #include "Luau/TxnLog.h"
#include "Luau/Type.h" #include "Luau/Type.h"
#include "Luau/TypeArena.h" #include "Luau/TypeArena.h"
#include "Luau/TypeCheckLimits.h"
#include "Luau/TypeFunction.h" #include "Luau/TypeFunction.h"
#include "Luau/TypePack.h" #include "Luau/TypePack.h"
#include "Luau/TypePath.h" #include "Luau/TypePath.h"
@ -22,7 +20,6 @@
#include <algorithm> #include <algorithm>
LUAU_FASTFLAGVARIABLE(DebugLuauSubtypingCheckPathValidity) LUAU_FASTFLAGVARIABLE(DebugLuauSubtypingCheckPathValidity)
LUAU_FASTFLAGVARIABLE(LuauSubtypingStopAtNormFail)
LUAU_FASTINTVARIABLE(LuauSubtypingReasoningLimit, 100) LUAU_FASTINTVARIABLE(LuauSubtypingReasoningLimit, 100)
LUAU_FASTFLAGVARIABLE(LuauSubtypingEnableReasoningLimit) LUAU_FASTFLAGVARIABLE(LuauSubtypingEnableReasoningLimit)
@ -34,7 +31,7 @@ struct VarianceFlipper
Subtyping::Variance* variance; Subtyping::Variance* variance;
Subtyping::Variance oldValue; Subtyping::Variance oldValue;
VarianceFlipper(Subtyping::Variance* v) explicit VarianceFlipper(Subtyping::Variance* v)
: variance(v) : variance(v)
, oldValue(*v) , oldValue(*v)
{ {
@ -316,7 +313,7 @@ struct ApplyMappedGenerics : Substitution
bool ignoreChildren(TypeId ty) override bool ignoreChildren(TypeId ty) override
{ {
if (get<ClassType>(ty)) if (get<ExternType>(ty))
return true; return true;
return ty->persistent; return ty->persistent;
@ -424,7 +421,7 @@ SubtypingResult Subtyping::isSubtype(TypeId subTy, TypeId superTy, NotNull<Scope
SubtypingResult result = isCovariantWith(env, subTy, superTy, scope); SubtypingResult result = isCovariantWith(env, subTy, superTy, scope);
if (FFlag::LuauSubtypingStopAtNormFail && result.normalizationTooComplex) if (result.normalizationTooComplex)
{ {
if (result.isCacheable) if (result.isCacheable)
resultCache[{subTy, superTy}] = result; resultCache[{subTy, superTy}] = result;
@ -610,7 +607,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
{ {
SubtypingResult semantic = isCovariantWith(env, normalizer->normalize(subTy), normalizer->normalize(superTy), scope); SubtypingResult semantic = isCovariantWith(env, normalizer->normalize(subTy), normalizer->normalize(superTy), scope);
if (FFlag::LuauSubtypingStopAtNormFail && semantic.normalizationTooComplex) if (semantic.normalizationTooComplex)
{ {
result = semantic; result = semantic;
} }
@ -630,7 +627,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
{ {
SubtypingResult semantic = isCovariantWith(env, normalizer->normalize(subTy), normalizer->normalize(superTy), scope); SubtypingResult semantic = isCovariantWith(env, normalizer->normalize(subTy), normalizer->normalize(superTy), scope);
if (FFlag::LuauSubtypingStopAtNormFail && semantic.normalizationTooComplex) if (semantic.normalizationTooComplex)
{ {
result = semantic; result = semantic;
} }
@ -745,9 +742,9 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
result = isCovariantWith(env, p, scope); result = isCovariantWith(env, p, scope);
else if (auto p = get2<MetatableType, TableType>(subTy, superTy)) else if (auto p = get2<MetatableType, TableType>(subTy, superTy))
result = isCovariantWith(env, p, scope); 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); 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); result = isCovariantWith(env, subTy, p.first, superTy, p.second, scope);
else if (auto p = get2<TableType, PrimitiveType>(subTy, superTy)) else if (auto p = get2<TableType, PrimitiveType>(subTy, superTy))
result = isCovariantWith(env, p, scope); result = isCovariantWith(env, p, scope);
@ -1110,7 +1107,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
{ {
SubtypingResult next = isCovariantWith(env, subTy, ty, scope); SubtypingResult next = isCovariantWith(env, subTy, ty, scope);
if (FFlag::LuauSubtypingStopAtNormFail && next.normalizationTooComplex) if (next.normalizationTooComplex)
return SubtypingResult{false, /* normalizationTooComplex */ true}; return SubtypingResult{false, /* normalizationTooComplex */ true};
if (next.isSubtype) if (next.isSubtype)
@ -1134,7 +1131,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Unio
{ {
subtypings.push_back(isCovariantWith(env, ty, superTy, scope).withSubComponent(TypePath::Index{i++, TypePath::Index::Variant::Union})); subtypings.push_back(isCovariantWith(env, ty, superTy, scope).withSubComponent(TypePath::Index{i++, TypePath::Index::Variant::Union}));
if (FFlag::LuauSubtypingStopAtNormFail && subtypings.back().normalizationTooComplex) if (subtypings.back().normalizationTooComplex)
return SubtypingResult{false, /* normalizationTooComplex */ true}; return SubtypingResult{false, /* normalizationTooComplex */ true};
} }
@ -1150,7 +1147,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
{ {
subtypings.push_back(isCovariantWith(env, subTy, ty, scope).withSuperComponent(TypePath::Index{i++, TypePath::Index::Variant::Intersection})); subtypings.push_back(isCovariantWith(env, subTy, ty, scope).withSuperComponent(TypePath::Index{i++, TypePath::Index::Variant::Intersection}));
if (FFlag::LuauSubtypingStopAtNormFail && subtypings.back().normalizationTooComplex) if (subtypings.back().normalizationTooComplex)
return SubtypingResult{false, /* normalizationTooComplex */ true}; return SubtypingResult{false, /* normalizationTooComplex */ true};
} }
@ -1166,7 +1163,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Inte
{ {
subtypings.push_back(isCovariantWith(env, ty, superTy, scope).withSubComponent(TypePath::Index{i++, TypePath::Index::Variant::Intersection})); subtypings.push_back(isCovariantWith(env, ty, superTy, scope).withSubComponent(TypePath::Index{i++, TypePath::Index::Variant::Intersection}));
if (FFlag::LuauSubtypingStopAtNormFail && subtypings.back().normalizationTooComplex) if (subtypings.back().normalizationTooComplex)
return SubtypingResult{false, /* normalizationTooComplex */ true}; return SubtypingResult{false, /* normalizationTooComplex */ true};
} }
@ -1337,7 +1334,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Type
} }
// the top class type is not actually a primitive type, so the negation of // the top class type is not actually a primitive type, so the negation of
// any one of them includes the top class type. // 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}; result = {true};
else if (auto p = get<PrimitiveType>(negatedTy); p && is<TableType, MetatableType>(subTy)) else if (auto p = get<PrimitiveType>(negatedTy); p && is<TableType, MetatableType>(subTy))
result = {p->type != PrimitiveType::Table}; result = {p->type != PrimitiveType::Table};
@ -1345,9 +1342,9 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Type
result = {p.second->type != PrimitiveType::Function}; result = {p.second->type != PrimitiveType::Function};
else if (auto p = get2<SingletonType, SingletonType>(subTy, negatedTy)) else if (auto p = get2<SingletonType, SingletonType>(subTy, negatedTy))
result = {*p.first != *p.second}; 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)); 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}; result = {true};
else if (is<ErrorType, FunctionType, TableType, MetatableType>(negatedTy)) else if (is<ErrorType, FunctionType, TableType, MetatableType>(negatedTy))
iceReporter->ice("attempting to negate a non-testable type"); iceReporter->ice("attempting to negate a non-testable type");
@ -1472,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( SubtypingResult Subtyping::isCovariantWith(
SubtypingEnvironment& env, SubtypingEnvironment& env,
TypeId subTy, TypeId subTy,
const ClassType* subClass, const ExternType* subExternType,
TypeId superTy, TypeId superTy,
const TableType* superTable, const TableType* superTable,
NotNull<Scope> scope NotNull<Scope> scope
@ -1492,7 +1489,7 @@ SubtypingResult Subtyping::isCovariantWith(
for (const auto& [name, prop] : superTable->props) 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)); result.andAlso(isCovariantWith(env, *classProp, prop, name, scope));
} }
@ -1662,7 +1659,7 @@ SubtypingResult Subtyping::isCovariantWith(
SubtypingResult result = isCovariantWith(env, subNorm->tops, superNorm->tops, scope); SubtypingResult result = isCovariantWith(env, subNorm->tops, superNorm->tops, scope);
result.andAlso(isCovariantWith(env, subNorm->booleans, superNorm->booleans, scope)); result.andAlso(isCovariantWith(env, subNorm->booleans, superNorm->booleans, scope));
result.andAlso( 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->errors, superNorm->errors, scope));
result.andAlso(isCovariantWith(env, subNorm->nils, superNorm->nils, scope)); result.andAlso(isCovariantWith(env, subNorm->nils, superNorm->nils, scope));
@ -1679,24 +1676,24 @@ SubtypingResult Subtyping::isCovariantWith(
SubtypingResult Subtyping::isCovariantWith( SubtypingResult Subtyping::isCovariantWith(
SubtypingEnvironment& env, SubtypingEnvironment& env,
const NormalizedClassType& subClass, const NormalizedExternType& subExternType,
const NormalizedClassType& superClass, const NormalizedExternType& superExternType,
NotNull<Scope> scope NotNull<Scope> scope
) )
{ {
for (const auto& [subClassTy, _] : subClass.classes) for (const auto& [subExternTypeTy, _] : subExternType.externTypes)
{ {
SubtypingResult result; 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) if (!result.isSubtype)
continue; continue;
for (TypeId negation : superNegations) 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) if (result.isSubtype)
break; break;
} }
@ -1711,17 +1708,17 @@ SubtypingResult Subtyping::isCovariantWith(
SubtypingResult Subtyping::isCovariantWith( SubtypingResult Subtyping::isCovariantWith(
SubtypingEnvironment& env, SubtypingEnvironment& env,
const NormalizedClassType& subClass, const NormalizedExternType& subExternType,
const TypeIds& superTables, const TypeIds& superTables,
NotNull<Scope> scope NotNull<Scope> scope
) )
{ {
for (const auto& [subClassTy, _] : subClass.classes) for (const auto& [subExternTypeTy, _] : subExternType.externTypes)
{ {
SubtypingResult result; SubtypingResult result;
for (TypeId superTableTy : superTables) for (TypeId superTableTy : superTables)
result.orElse(isCovariantWith(env, subClassTy, superTableTy, scope)); result.orElse(isCovariantWith(env, subExternTypeTy, superTableTy, scope));
if (!result.isSubtype) if (!result.isSubtype)
return result; return result;
@ -1812,7 +1809,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Type
{ {
results.back().orElse(isCovariantWith(env, subTy, superTy, scope)); results.back().orElse(isCovariantWith(env, subTy, superTy, scope));
if (FFlag::LuauSubtypingStopAtNormFail && results.back().normalizationTooComplex) if (results.back().normalizationTooComplex)
return SubtypingResult{false, /* normalizationTooComplex */ true}; return SubtypingResult{false, /* normalizationTooComplex */ true};
} }
} }

View file

@ -13,7 +13,6 @@
#include "Luau/TypeUtils.h" #include "Luau/TypeUtils.h"
#include "Luau/Unifier2.h" #include "Luau/Unifier2.h"
LUAU_FASTFLAGVARIABLE(LuauBidirectionalInferenceUpcast)
LUAU_FASTFLAGVARIABLE(LuauBidirectionalInferenceCollectIndexerTypes) LUAU_FASTFLAGVARIABLE(LuauBidirectionalInferenceCollectIndexerTypes)
LUAU_FASTFLAGVARIABLE(LuauBidirectionalFailsafe) LUAU_FASTFLAGVARIABLE(LuauBidirectionalFailsafe)
LUAU_FASTFLAGVARIABLE(LuauBidirectionalInferenceElideAssert) LUAU_FASTFLAGVARIABLE(LuauBidirectionalInferenceElideAssert)
@ -141,15 +140,10 @@ TypeId matchLiteralType(
*/ */
if (!isLiteral(expr)) if (!isLiteral(expr))
{
if (FFlag::LuauBidirectionalInferenceUpcast)
{ {
auto result = subtyping->isSubtype(/*subTy=*/exprType, /*superTy=*/expectedType, unifier->scope); auto result = subtyping->isSubtype(/*subTy=*/exprType, /*superTy=*/expectedType, unifier->scope);
return result.isSubtype ? expectedType : exprType; return result.isSubtype ? expectedType : exprType;
} }
else
return exprType;
}
expectedType = follow(expectedType); expectedType = follow(expectedType);
exprType = follow(exprType); exprType = follow(exprType);
@ -239,7 +233,7 @@ TypeId matchLiteralType(
} }
if (FFlag::LuauBidirectionalInferenceUpcast && expr->is<AstExprFunction>()) if (expr->is<AstExprFunction>())
{ {
// TODO: Push argument / return types into the lambda. For now, just do // TODO: Push argument / return types into the lambda. For now, just do
// the non-literal thing: check for a subtype and upcast if valid. // the non-literal thing: check for a subtype and upcast if valid.

View file

@ -299,9 +299,9 @@ void StateDot::visitChildren(TypeId ty, int index)
finishNodeLabel(ty); finishNodeLabel(ty);
finishNode(); 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); finishNodeLabel(ty);
finishNode(); finishNode();

View file

@ -19,8 +19,11 @@
#include <stdexcept> #include <stdexcept>
#include <string> #include <string>
LUAU_FASTFLAGVARIABLE(LuauEnableDenseTableAlias)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAGVARIABLE(LuauSyntheticErrors) LUAU_FASTFLAGVARIABLE(LuauSyntheticErrors)
LUAU_FASTFLAGVARIABLE(LuauStringPartLengthLimit)
/* /*
* Enables increasing levels of verbosity for Luau type names when stringifying. * Enables increasing levels of verbosity for Luau type names when stringifying.
@ -120,7 +123,7 @@ struct FindCyclicTypes final : TypeVisitor
return true; return true;
} }
bool visit(TypeId ty, const ClassType&) override bool visit(TypeId ty, const ExternType&) override
{ {
return false; return false;
} }
@ -719,7 +722,13 @@ struct TypeStringifier
if (ttv.boundTo) if (ttv.boundTo)
return stringify(*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) if (ttv.name)
{ {
@ -742,6 +751,10 @@ struct TypeStringifier
stringify(ttv.instantiatedTypeParams, ttv.instantiatedTypePackParams); stringify(ttv.instantiatedTypeParams, ttv.instantiatedTypePackParams);
return; return;
} }
}
if (!state.exhaustive)
{
if (ttv.syntheticName) if (ttv.syntheticName)
{ {
state.result.invalid = true; state.result.invalid = true;
@ -880,9 +893,9 @@ struct TypeStringifier
state.emit(" }"); 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&) void operator()(TypeId, const AnyType&)
@ -910,6 +923,9 @@ struct TypeStringifier
bool hasNonNilDisjunct = false; bool hasNonNilDisjunct = false;
std::vector<std::string> results = {}; std::vector<std::string> results = {};
size_t resultsLength = 0;
bool lengthLimitHit = false;
for (auto el : &uv) for (auto el : &uv)
{ {
el = follow(el); el = follow(el);
@ -936,14 +952,34 @@ struct TypeStringifier
if (needParens) if (needParens)
state.emit(")"); state.emit(")");
if (FFlag::LuauStringPartLengthLimit)
resultsLength += state.result.name.length();
results.push_back(std::move(state.result.name)); results.push_back(std::move(state.result.name));
state.result.name = std::move(saved); state.result.name = std::move(saved);
if (FFlag::LuauStringPartLengthLimit)
{
lengthLimitHit = state.opts.maxTypeLength > 0 && resultsLength > state.opts.maxTypeLength;
if (lengthLimitHit)
break;
}
} }
state.unsee(&uv); state.unsee(&uv);
if (FFlag::LuauStringPartLengthLimit)
{
if (!lengthLimitHit && !FFlag::DebugLuauToStringNoLexicalSort)
std::sort(results.begin(), results.end());
}
else
{
if (!FFlag::DebugLuauToStringNoLexicalSort) if (!FFlag::DebugLuauToStringNoLexicalSort)
std::sort(results.begin(), results.end()); std::sort(results.begin(), results.end());
}
if (optional && results.size() > 1) if (optional && results.size() > 1)
state.emit("("); state.emit("(");
@ -987,6 +1023,9 @@ struct TypeStringifier
} }
std::vector<std::string> results = {}; std::vector<std::string> results = {};
size_t resultsLength = 0;
bool lengthLimitHit = false;
for (auto el : uv.parts) for (auto el : uv.parts)
{ {
el = follow(el); el = follow(el);
@ -1003,14 +1042,34 @@ struct TypeStringifier
if (needParens) if (needParens)
state.emit(")"); state.emit(")");
if (FFlag::LuauStringPartLengthLimit)
resultsLength += state.result.name.length();
results.push_back(std::move(state.result.name)); results.push_back(std::move(state.result.name));
state.result.name = std::move(saved); state.result.name = std::move(saved);
if (FFlag::LuauStringPartLengthLimit)
{
lengthLimitHit = state.opts.maxTypeLength > 0 && resultsLength > state.opts.maxTypeLength;
if (lengthLimitHit)
break;
}
} }
state.unsee(&uv); state.unsee(&uv);
if (FFlag::LuauStringPartLengthLimit)
{
if (!lengthLimitHit && !FFlag::DebugLuauToStringNoLexicalSort)
std::sort(results.begin(), results.end());
}
else
{
if (!FFlag::DebugLuauToStringNoLexicalSort) if (!FFlag::DebugLuauToStringNoLexicalSort)
std::sort(results.begin(), results.end()); std::sort(results.begin(), results.end());
}
bool first = true; bool first = true;
bool shouldPlaceOnNewlines = results.size() > state.opts.compositeTypesSingleLineLimit || isOverloadedFunction(ty); bool shouldPlaceOnNewlines = results.size() > state.opts.compositeTypesSingleLineLimit || isOverloadedFunction(ty);

View file

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

View file

@ -12,9 +12,9 @@
LUAU_FASTFLAG(LuauStoreCSTData2) LUAU_FASTFLAG(LuauStoreCSTData2)
LUAU_FASTFLAG(LuauAstTypeGroup3) LUAU_FASTFLAG(LuauAstTypeGroup3)
LUAU_FASTFLAG(LuauFixDoBlockEndLocation)
LUAU_FASTFLAG(LuauParseOptionalAsNode2) LUAU_FASTFLAG(LuauParseOptionalAsNode2)
LUAU_FASTFLAG(LuauFixFunctionWithAttributesStartLocation) LUAU_FASTFLAG(LuauFixFunctionWithAttributesStartLocation)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
namespace 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); advance(annotation.location.begin);
if (const AstTypePackVariadic* variadicTp = annotation.as<AstTypePackVariadic>()) if (const AstTypePackVariadic* variadicTp = annotation.as<AstTypePackVariadic>())
@ -349,7 +349,7 @@ struct Printer_DEPRECATED
else if (const AstTypePackExplicit* explicitTp = annotation.as<AstTypePackExplicit>()) else if (const AstTypePackExplicit* explicitTp = annotation.as<AstTypePackExplicit>())
{ {
LUAU_ASSERT(!forVarArg); LUAU_ASSERT(!forVarArg);
visualizeTypeList(explicitTp->typeList, true); visualizeTypeList(explicitTp->typeList, unconditionallyParenthesize);
} }
else else
{ {
@ -705,8 +705,6 @@ struct Printer_DEPRECATED
writer.keyword("do"); writer.keyword("do");
for (const auto& s : block->body) for (const auto& s : block->body)
visualize(*s); visualize(*s);
if (!FFlag::LuauFixDoBlockEndLocation)
writer.advance(block->location.end);
writeEnd(program.location); writeEnd(program.location);
} }
else if (const auto& a = program.as<AstStatIf>()) else if (const auto& a = program.as<AstStatIf>())
@ -1065,12 +1063,15 @@ struct Printer_DEPRECATED
writer.symbol(")"); writer.symbol(")");
if (writeTypes && func.returnAnnotation) if (writeTypes && (FFlag::LuauStoreReturnTypesAsPackOnAst ? func.returnAnnotation != nullptr : func.returnAnnotation_DEPRECATED.has_value()))
{ {
writer.symbol(":"); writer.symbol(":");
writer.space(); writer.space();
visualizeTypeList(*func.returnAnnotation, false); if (FFlag::LuauStoreReturnTypesAsPackOnAst)
visualizeTypePackAnnotation(*func.returnAnnotation, false, false);
else
visualizeTypeList(*func.returnAnnotation_DEPRECATED, false);
} }
visualizeBlock(*func.body); visualizeBlock(*func.body);
@ -1174,7 +1175,10 @@ struct Printer_DEPRECATED
} }
writer.symbol("->"); 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>()) 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); advance(annotation.location.begin);
if (const AstTypePackVariadic* variadicTp = annotation.as<AstTypePackVariadic>()) if (const AstTypePackVariadic* variadicTp = annotation.as<AstTypePackVariadic>())
@ -1390,10 +1394,10 @@ struct Printer
LUAU_ASSERT(!forVarArg); LUAU_ASSERT(!forVarArg);
if (const auto cstNode = lookupCstNode<CstTypePackExplicit>(explicitTp)) if (const auto cstNode = lookupCstNode<CstTypePackExplicit>(explicitTp))
visualizeTypeList( visualizeTypeList(
explicitTp->typeList, true, cstNode->openParenthesesPosition, cstNode->closeParenthesesPosition, cstNode->commaPositions explicitTp->typeList, FFlag::LuauStoreReturnTypesAsPackOnAst ? cstNode->hasParentheses : true, cstNode->openParenthesesPosition, cstNode->closeParenthesesPosition, cstNode->commaPositions
); );
else else
visualizeTypeList(explicitTp->typeList, true); visualizeTypeList(explicitTp->typeList, unconditionallyParenthesize);
} }
else else
{ {
@ -2316,8 +2320,6 @@ struct Printer
{ {
const auto cstNode = lookupCstNode<CstExprFunction>(&func); 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) if (func.generics.size > 0 || func.genericPacks.size > 0)
{ {
CommaSeparatorInserter comma(writer, cstNode ? cstNode->genericsCommaPositions.begin() : nullptr); CommaSeparatorInserter comma(writer, cstNode ? cstNode->genericsCommaPositions.begin() : nullptr);
@ -2383,14 +2385,23 @@ struct Printer
advanceBefore(func.argLocation->end, 1); advanceBefore(func.argLocation->end, 1);
writer.symbol(")"); writer.symbol(")");
if (writeTypes && func.returnAnnotation) if (writeTypes && FFlag::LuauStoreReturnTypesAsPackOnAst ? func.returnAnnotation != nullptr : func.returnAnnotation_DEPRECATED.has_value())
{ {
if (cstNode) if (cstNode)
advance(cstNode->returnSpecifierPosition); advance(cstNode->returnSpecifierPosition);
writer.symbol(":"); 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); visualizeBlock(*func.body);
@ -2573,7 +2584,10 @@ struct Printer
if (cstNode) if (cstNode)
advance(cstNode->returnArrowPosition); advance(cstNode->returnArrowPosition);
writer.symbol("->"); 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>()) else if (const auto& a = typeAnnotation.as<AstTypeTable>())
{ {
@ -2646,6 +2660,7 @@ struct Printer
{ {
advance(item.indexerOpenPosition); advance(item.indexerOpenPosition);
writer.symbol("["); writer.symbol("[");
advance(item.stringPosition);
writer.sourceString( writer.sourceString(
std::string_view(item.stringInfo->sourceString.data, item.stringInfo->sourceString.size), std::string_view(item.stringInfo->sourceString.data, item.stringInfo->sourceString.size),
item.stringInfo->quoteStyle, item.stringInfo->quoteStyle,

View file

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

View file

@ -50,33 +50,6 @@ TypeId TypeArena::freshType(NotNull<BuiltinTypes> builtins, Scope* scope, TypeLe
return allocated; 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 TypeArena::freshTypePack(Scope* scope, Polarity polarity)
{ {
TypePackId allocated = typePacks.allocate(FreeTypePack{scope, polarity}); TypePackId allocated = typePacks.allocate(FreeTypePack{scope, polarity});

View file

@ -14,6 +14,7 @@
#include <string> #include <string>
LUAU_FASTFLAG(LuauStoreCSTData2) LUAU_FASTFLAG(LuauStoreCSTData2)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
static char* allocateString(Luau::Allocator& allocator, std::string_view contents) static char* allocateString(Luau::Allocator& allocator, std::string_view contents)
{ {
@ -219,21 +220,21 @@ public:
return Luau::visit(*this, mtv.table->ty); return Luau::visit(*this, mtv.table->ty);
} }
AstType* operator()(const ClassType& ctv) AstType* operator()(const ExternType& etv)
{ {
RecursionCounter counter(&count); 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()); return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName{name}, std::nullopt, Location());
AstArray<AstTableProp> props; AstArray<AstTableProp> props;
props.size = ctv.props.size(); props.size = etv.props.size();
props.data = static_cast<AstTableProp*>(allocator->allocate(sizeof(AstTableProp) * props.size)); props.data = static_cast<AstTableProp*>(allocator->allocate(sizeof(AstTableProp) * props.size));
int idx = 0; int idx = 0;
for (const auto& [propName, prop] : ctv.props) for (const auto& [propName, prop] : etv.props)
{ {
char* name = allocateString(*allocator, propName); char* name = allocateString(*allocator, propName);
@ -244,13 +245,13 @@ public:
} }
AstTableIndexer* indexer = nullptr; AstTableIndexer* indexer = nullptr;
if (ctv.indexer) if (etv.indexer)
{ {
RecursionCounter counter(&count); RecursionCounter counter(&count);
indexer = allocator->alloc<AstTableIndexer>(); indexer = allocator->alloc<AstTableIndexer>();
indexer->indexType = Luau::visit(*this, ctv.indexer->indexType->ty); indexer->indexType = Luau::visit(*this, etv.indexer->indexType->ty);
indexer->resultType = Luau::visit(*this, ctv.indexer->indexResultType->ty); indexer->resultType = Luau::visit(*this, etv.indexer->indexResultType->ty);
} }
return allocator->alloc<AstTypeTable>(Location(), props, indexer); return allocator->alloc<AstTypeTable>(Location(), props, indexer);
@ -328,10 +329,20 @@ public:
if (retTail) if (retTail)
retTailAnnotation = rehydrate(*retTail); retTailAnnotation = rehydrate(*retTail);
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>( return allocator->alloc<AstTypeFunction>(
Location(), generics, genericPacks, AstTypeList{argTypes, argTailAnnotation}, argNames, AstTypeList{returnTypes, retTailAnnotation} Location(), generics, genericPacks, AstTypeList{argTypes, argTailAnnotation}, argNames, AstTypeList{returnTypes, retTailAnnotation}
); );
} }
}
AstType* operator()(const ErrorType&) AstType* operator()(const ErrorType&)
{ {
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName("Unifiable<Error>"), std::nullopt, Location()); return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName("Unifiable<Error>"), std::nullopt, Location());
@ -585,6 +596,8 @@ public:
visitLocal(arg); visitLocal(arg);
} }
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
if (!fn->returnAnnotation) if (!fn->returnAnnotation)
{ {
if (auto result = getScope(fn->body->location)) if (auto result = getScope(fn->body->location))
@ -597,7 +610,26 @@ public:
if (tail) if (tail)
variadicAnnotation = TypeRehydrationVisitor(allocator, &syntheticNames).rehydrate(*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(DebugLuauMagicTypes)
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
LUAU_FASTFLAGVARIABLE(LuauImproveTypePathsInErrors) LUAU_FASTFLAGVARIABLE(LuauImproveTypePathsInErrors)
LUAU_FASTFLAG(LuauUserTypeFunTypecheck) LUAU_FASTFLAG(LuauUserTypeFunTypecheck)
LUAU_FASTFLAGVARIABLE(LuauTypeCheckerAcceptNumberConcats) LUAU_FASTFLAGVARIABLE(LuauTypeCheckerAcceptNumberConcats)
LUAU_FASTFLAGVARIABLE(LuauTypeCheckerStricterIndexCheck)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
namespace Luau namespace Luau
{ {
@ -661,7 +662,7 @@ void TypeChecker2::visit(AstStat* stat)
return visit(s); return visit(s);
else if (auto s = stat->as<AstStatDeclareGlobal>()) else if (auto s = stat->as<AstStatDeclareGlobal>())
return visit(s); return visit(s);
else if (auto s = stat->as<AstStatDeclareClass>()) else if (auto s = stat->as<AstStatDeclareExternType>())
return visit(s); return visit(s);
else if (auto s = stat->as<AstStatError>()) else if (auto s = stat->as<AstStatError>())
return visit(s); return visit(s);
@ -1221,7 +1222,10 @@ void TypeChecker2::visit(AstStatDeclareFunction* stat)
{ {
visitGenerics(stat->generics, stat->genericPacks); visitGenerics(stat->generics, stat->genericPacks);
visit(stat->params); visit(stat->params);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
visit(stat->retTypes); visit(stat->retTypes);
else
visit(stat->retTypes_DEPRECATED);
} }
void TypeChecker2::visit(AstStatDeclareGlobal* stat) void TypeChecker2::visit(AstStatDeclareGlobal* stat)
@ -1229,9 +1233,9 @@ void TypeChecker2::visit(AstStatDeclareGlobal* stat)
visit(stat->type); 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); visit(prop.ty);
} }
@ -1675,12 +1679,12 @@ void TypeChecker2::visit(AstExprIndexExpr* indexExpr, ValueContext context)
{ {
return indexExprMetatableHelper(indexExpr, mt, exprType, indexType); return indexExprMetatableHelper(indexExpr, mt, exprType, indexType);
} }
else if (auto cls = get<ClassType>(exprType)) else if (auto cls = get<ExternType>(exprType))
{ {
if (cls->indexer) if (cls->indexer)
testIsSubtype(indexType, cls->indexer->indexType, indexExpr->index->location); testIsSubtype(indexType, cls->indexer->indexType, indexExpr->index->location);
else else
reportError(DynamicPropertyLookupOnClassesUnsafe{exprType}, indexExpr->location); reportError(DynamicPropertyLookupOnExternTypesUnsafe{exprType}, indexExpr->location);
} }
else if (get<UnionType>(exprType) && isOptional(exprType)) else if (get<UnionType>(exprType) && isOptional(exprType))
{ {
@ -1821,8 +1825,16 @@ void TypeChecker2::visit(AstExprFunction* fn)
visit(fn->body); visit(fn->body);
// we need to typecheck the return annotation itself, if it exists. // we need to typecheck the return annotation itself, if it exists.
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
if (fn->returnAnnotation) if (fn->returnAnnotation)
visit(*fn->returnAnnotation); visit(fn->returnAnnotation);
}
else
{
if (fn->returnAnnotation_DEPRECATED)
visit(*fn->returnAnnotation_DEPRECATED);
}
// If the function type has a function annotation, we need to see if we can suggest an annotation // If the function type has a function annotation, we need to see if we can suggest an annotation
@ -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 // 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 // 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, // 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 (!(get<TableType>(leftType) || get<TableType>(rightType)))
if (!leftMt.has_value() || !rightMt.has_value()) if (!leftMt.has_value() || !rightMt.has_value())
matches = matches || typesHaveIntersection != NormalizationResult::False; matches = matches || typesHaveIntersection != NormalizationResult::False;
@ -2101,10 +2113,7 @@ TypeId TypeChecker2::visit(AstExprBinary* expr, AstNode* overrideKey)
} }
else else
{ {
expectedRets = module->internalTypes.addTypePack( expectedRets = module->internalTypes.addTypePack({module->internalTypes.freshType(builtinTypes, scope, TypeLevel{})});
{FFlag::LuauFreeTypesMustHaveBounds ? module->internalTypes.freshType(builtinTypes, scope, TypeLevel{})
: module->internalTypes.freshType_DEPRECATED(scope, TypeLevel{})}
);
} }
TypeId expectedTy = module->internalTypes.addType(FunctionType(expectedArgs, expectedRets)); TypeId expectedTy = module->internalTypes.addType(FunctionType(expectedArgs, expectedRets));
@ -2367,8 +2376,7 @@ TypeId TypeChecker2::flattenPack(TypePackId pack)
return *fst; return *fst;
else if (auto ftp = get<FreeTypePack>(pack)) else if (auto ftp = get<FreeTypePack>(pack))
{ {
TypeId result = FFlag::LuauFreeTypesMustHaveBounds ? module->internalTypes.freshType(builtinTypes, ftp->scope) TypeId result = module->internalTypes.freshType(builtinTypes, ftp->scope);
: module->internalTypes.addType(FreeType{ftp->scope});
TypePackId freeTail = module->internalTypes.addTypePack(FreeTypePack{ftp->scope}); TypePackId freeTail = module->internalTypes.addTypePack(FreeTypePack{ftp->scope});
TypePack* resultPack = emplaceTypePack<TypePack>(asMutable(pack)); TypePack* resultPack = emplaceTypePack<TypePack>(asMutable(pack));
@ -2613,7 +2621,10 @@ void TypeChecker2::visit(AstTypeFunction* ty)
{ {
visitGenerics(ty->generics, ty->genericPacks); visitGenerics(ty->generics, ty->genericPacks);
visit(ty->argTypes); visit(ty->argTypes);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
visit(ty->returnTypes); visit(ty->returnTypes);
else
visit(ty->returnTypes_DEPRECATED);
} }
void TypeChecker2::visit(AstTypeTypeof* ty) void TypeChecker2::visit(AstTypeTypeof* ty)
@ -2937,7 +2948,7 @@ PropertyTypes TypeChecker2::lookupProp(
if (normValid) if (normValid)
{ {
for (const auto& [ty, _negations] : norm->classes.classes) for (const auto& [ty, _negations] : norm->externTypes.externTypes)
{ {
fetch(ty); fetch(ty);
@ -3032,10 +3043,10 @@ void TypeChecker2::checkIndexTypeFromType(
if (propTypes.foundOneProp()) if (propTypes.foundOneProp())
reportError(MissingUnionProperty{tableTy, propTypes.missingProp, prop}, location); reportError(MissingUnionProperty{tableTy, propTypes.missingProp, prop}, location);
// For class LValues, we don't want to report an extension error, // 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 // shape. We instead want to report the unknown property error of
// the `else` branch. // 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); const auto lvPropTypes = lookupProp(norm.get(), prop, ValueContext::RValue, location, astIndexExprType, dummy);
if (lvPropTypes.foundOneProp() && lvPropTypes.noneMissingProp()) if (lvPropTypes.foundOneProp() && lvPropTypes.noneMissingProp())
@ -3045,7 +3056,7 @@ void TypeChecker2::checkIndexTypeFromType(
else else
reportError(CannotExtendTable{tableTy, CannotExtendTable::Property, prop}, location); 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); const auto rvPropTypes = lookupProp(norm.get(), prop, ValueContext::LValue, location, astIndexExprType, dummy);
if (rvPropTypes.foundOneProp() && rvPropTypes.noneMissingProp()) if (rvPropTypes.foundOneProp() && rvPropTypes.noneMissingProp())
@ -3098,19 +3109,25 @@ PropertyType TypeChecker2::hasIndexTypeFromType(
return {NormalizationResult::True, {tt->indexer->indexResultType}}; return {NormalizationResult::True, {tt->indexer->indexResultType}};
} }
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 // 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 // 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. // 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}}; 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 // 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]) // We need to check if the type of the index expression foo (x[foo])
// is compatible with the indexer's indexType // is compatible with the indexer's indexType
// Construct the intersection and test inhabitedness! // 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}; return {NormalizationResult::True, context == ValueContext::LValue ? property->writeTy : property->readTy};
if (cls->indexer) if (cls->indexer)
{ {
@ -3183,17 +3200,17 @@ void TypeChecker2::diagnoseMissingTableKey(UnknownProperty* utk, TypeErrorData&
if (auto ttv = getTableType(utk->table)) if (auto ttv = getTableType(utk->table))
accumulate(ttv->props); 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; break;
ctv = get<ClassType>(*ctv->parent); etv = get<ExternType>(*etv->parent);
LUAU_ASSERT(ctv); LUAU_ASSERT(etv);
} }
} }

View file

@ -48,7 +48,8 @@ LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFamilyUseGuesserDepth, -1);
LUAU_FASTFLAG(DebugLuauEqSatSimplification) LUAU_FASTFLAG(DebugLuauEqSatSimplification)
LUAU_FASTFLAG(LuauTypeFunResultInAutocomplete) LUAU_FASTFLAG(LuauTypeFunResultInAutocomplete)
LUAU_FASTFLAG(LuauNonReentrantGeneralization) LUAU_FASTFLAG(LuauNonReentrantGeneralization2)
LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
LUAU_FASTFLAGVARIABLE(DebugLuauLogTypeFamilies) LUAU_FASTFLAGVARIABLE(DebugLuauLogTypeFamilies)
LUAU_FASTFLAGVARIABLE(LuauMetatableTypeFunctions) LUAU_FASTFLAGVARIABLE(LuauMetatableTypeFunctions)
@ -57,14 +58,15 @@ LUAU_FASTFLAGVARIABLE(LuauIndexTypeFunctionFunctionMetamethods)
LUAU_FASTFLAGVARIABLE(LuauIntersectNotNil) LUAU_FASTFLAGVARIABLE(LuauIntersectNotNil)
LUAU_FASTFLAGVARIABLE(LuauSkipNoRefineDuringRefinement) LUAU_FASTFLAGVARIABLE(LuauSkipNoRefineDuringRefinement)
LUAU_FASTFLAGVARIABLE(LuauMetatablesHaveLength) LUAU_FASTFLAGVARIABLE(LuauMetatablesHaveLength)
LUAU_FASTFLAGVARIABLE(LuauDontForgetToReduceUnionFunc)
LUAU_FASTFLAGVARIABLE(LuauSearchForRefineableType)
LUAU_FASTFLAGVARIABLE(LuauIndexAnyIsAny) LUAU_FASTFLAGVARIABLE(LuauIndexAnyIsAny)
LUAU_FASTFLAGVARIABLE(LuauFixCyclicIndexInIndexer) LUAU_FASTFLAGVARIABLE(LuauFixCyclicIndexInIndexer)
LUAU_FASTFLAGVARIABLE(LuauSimplyRefineNotNil) LUAU_FASTFLAGVARIABLE(LuauSimplyRefineNotNil)
LUAU_FASTFLAGVARIABLE(LuauIndexDeferPendingIndexee) LUAU_FASTFLAGVARIABLE(LuauIndexDeferPendingIndexee)
LUAU_FASTFLAGVARIABLE(LuauNewTypeFunReductionChecks2) LUAU_FASTFLAGVARIABLE(LuauNewTypeFunReductionChecks2)
LUAU_FASTFLAGVARIABLE(LuauReduceUnionFollowUnionType) LUAU_FASTFLAGVARIABLE(LuauReduceUnionFollowUnionType)
LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect)
LUAU_FASTFLAGVARIABLE(LuauNarrowIntersectionNevers)
LUAU_FASTFLAGVARIABLE(LuauRefineWaitForBlockedTypesInTarget)
namespace Luau namespace Luau
{ {
@ -103,7 +105,7 @@ struct InstanceCollector_DEPRECATED : TypeOnceVisitor
cyclicInstance.push_back(t); cyclicInstance.push_back(t);
} }
bool visit(TypeId ty, const ClassType&) override bool visit(TypeId ty, const ExternType&) override
{ {
return false; return false;
} }
@ -179,7 +181,7 @@ struct InstanceCollector : TypeOnceVisitor
} }
} }
bool visit(TypeId ty, const ClassType&) override bool visit(TypeId ty, const ExternType&) override
{ {
return false; return false;
} }
@ -267,7 +269,7 @@ struct UnscopedGenericFinder : TypeOnceVisitor
return false; return false;
} }
bool visit(TypeId ty, const ClassType&) override bool visit(TypeId ty, const ExternType&) override
{ {
return false; return false;
} }
@ -309,9 +311,22 @@ struct TypeFunctionReducer
enum class SkipTestResult enum class SkipTestResult
{ {
/// If a type function is cyclic, it cannot be reduced, but maybe we can
/// make a guess and offer a suggested annotation to the user.
CyclicTypeFunction, CyclicTypeFunction,
/// Indicase that we will not be able to reduce this type function this
/// time. Constraint resolution may cause this type function to become
/// reducible later.
Irreducible, Irreducible,
/// Some type functions can operate on generic parameters
Generic,
/// We might be able to reduce this type function, but not yet.
Defer, Defer,
/// We can attempt to reduce this type function right now.
Okay, Okay,
}; };
@ -334,6 +349,9 @@ struct TypeFunctionReducer
} }
else if (is<GenericType>(ty)) else if (is<GenericType>(ty))
{ {
if (FFlag::DebugLuauGreedyGeneralization)
return SkipTestResult::Generic;
else
return SkipTestResult::Irreducible; return SkipTestResult::Irreducible;
} }
@ -353,6 +371,9 @@ struct TypeFunctionReducer
} }
else if (is<GenericTypePack>(ty)) else if (is<GenericTypePack>(ty))
{ {
if (FFlag::DebugLuauGreedyGeneralization)
return SkipTestResult::Generic;
else
return SkipTestResult::Irreducible; return SkipTestResult::Irreducible;
} }
@ -435,7 +456,7 @@ struct TypeFunctionReducer
{ {
SkipTestResult skip = testForSkippability(p); SkipTestResult skip = testForSkippability(p);
if (skip == SkipTestResult::Irreducible) if (skip == SkipTestResult::Irreducible || (skip == SkipTestResult::Generic && !tfit->function->canReduceGenerics))
{ {
if (FFlag::DebugLuauLogTypeFamilies) if (FFlag::DebugLuauLogTypeFamilies)
printf("%s is irreducible due to a dependency on %s\n", toString(subject, {true}).c_str(), toString(p, {true}).c_str()); printf("%s is irreducible due to a dependency on %s\n", toString(subject, {true}).c_str(), toString(p, {true}).c_str());
@ -461,7 +482,7 @@ struct TypeFunctionReducer
{ {
SkipTestResult skip = testForSkippability(p); SkipTestResult skip = testForSkippability(p);
if (skip == SkipTestResult::Irreducible) if (skip == SkipTestResult::Irreducible || (skip == SkipTestResult::Generic && !tfit->function->canReduceGenerics))
{ {
if (FFlag::DebugLuauLogTypeFamilies) if (FFlag::DebugLuauLogTypeFamilies)
printf("%s is irreducible due to a dependency on %s\n", toString(subject, {true}).c_str(), toString(p, {true}).c_str()); printf("%s is irreducible due to a dependency on %s\n", toString(subject, {true}).c_str(), toString(p, {true}).c_str());
@ -822,7 +843,7 @@ static std::optional<TypeFunctionReductionResult<TypeId>> tryDistributeTypeFunct
{ {
arguments[unionIndex] = option; 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()); blockedTypes.insert(blockedTypes.end(), result.blockedTypes.begin(), result.blockedTypes.end());
if (result.reductionStatus != Reduction::MaybeOk) if (result.reductionStatus != Reduction::MaybeOk)
reductionStatus = result.reductionStatus; reductionStatus = result.reductionStatus;
@ -847,7 +868,7 @@ static std::optional<TypeFunctionReductionResult<TypeId>> tryDistributeTypeFunct
{}, {},
}); });
if (FFlag::LuauDontForgetToReduceUnionFunc && ctx->solver) if (ctx->solver)
ctx->pushConstraint(ReduceConstraint{resultTy}); ctx->pushConstraint(ReduceConstraint{resultTy});
return {{resultTy, Reduction::MaybeOk, {}, {}}}; return {{resultTy, Reduction::MaybeOk, {}, {}}};
@ -886,7 +907,7 @@ struct FindUserTypeFunctionBlockers : TypeOnceVisitor
return true; return true;
} }
bool visit(TypeId ty, const ClassType&) override bool visit(TypeId ty, const ExternType&) override
{ {
return false; return false;
} }
@ -1221,7 +1242,7 @@ TypeFunctionReductionResult<TypeId> unmTypeFunction(
if (isPending(operandTy, ctx->solver)) if (isPending(operandTy, ctx->solver))
return {std::nullopt, Reduction::MaybeOk, {operandTy}, {}}; return {std::nullopt, Reduction::MaybeOk, {operandTy}, {}};
if (FFlag::LuauNonReentrantGeneralization) if (FFlag::LuauNonReentrantGeneralization2)
operandTy = follow(operandTy); operandTy = follow(operandTy);
std::shared_ptr<const NormalizedType> normTy = ctx->normalizer->normalize(operandTy); std::shared_ptr<const NormalizedType> normTy = ctx->normalizer->normalize(operandTy);
@ -1818,10 +1839,20 @@ TypeFunctionReductionResult<TypeId> orTypeFunction(
return {rhsTy, Reduction::MaybeOk, {}, {}}; return {rhsTy, Reduction::MaybeOk, {}, {}};
// check to see if both operand types are resolved enough, and wait to reduce if not // check to see if both operand types are resolved enough, and wait to reduce if not
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)) if (isPending(lhsTy, ctx->solver))
return {std::nullopt, Reduction::MaybeOk, {lhsTy}, {}}; return {std::nullopt, Reduction::MaybeOk, {lhsTy}, {}};
else if (isPending(rhsTy, ctx->solver)) else if (isPending(rhsTy, ctx->solver))
return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}}; 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. // 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); SimplifyResult filteredLhs = simplifyIntersection(ctx->builtins, ctx->arena, lhsTy, ctx->builtins->truthyType);
@ -1855,10 +1886,20 @@ static TypeFunctionReductionResult<TypeId> comparisonTypeFunction(
if (lhsTy == instance || rhsTy == instance) if (lhsTy == instance || rhsTy == instance)
return {ctx->builtins->neverType, Reduction::MaybeOk, {}, {}}; return {ctx->builtins->neverType, Reduction::MaybeOk, {}, {}};
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)) if (isPending(lhsTy, ctx->solver))
return {std::nullopt, Reduction::MaybeOk, {lhsTy}, {}}; return {std::nullopt, Reduction::MaybeOk, {lhsTy}, {}};
else if (isPending(rhsTy, ctx->solver)) else if (isPending(rhsTy, ctx->solver))
return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}}; return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}};
}
// Algebra Reduction Rules for comparison type functions // Algebra Reduction Rules for comparison type functions
// Note that comparing to never tells you nothing about the other operand // Note that comparing to never tells you nothing about the other operand
@ -2108,7 +2149,7 @@ struct FindRefinementBlockers : TypeOnceVisitor
return false; return false;
} }
bool visit(TypeId ty, const ClassType&) override bool visit(TypeId ty, const ExternType&) override
{ {
return false; return false;
} }
@ -2163,6 +2204,44 @@ struct ContainsRefinableType : TypeOnceVisitor
} }
}; };
namespace
{
bool isApproximateFalsy(TypeId ty)
{
ty = follow(ty);
bool seenNil = false;
bool seenFalse = false;
if (auto ut = get<UnionType>(ty))
{
for (auto option : ut)
{
if (auto pt = get<PrimitiveType>(option); pt && pt->type == PrimitiveType::NilType)
seenNil = true;
else if (auto st = get<SingletonType>(option); st && st->variant == BooleanSingleton{false})
seenFalse = true;
else
return false;
}
}
return seenFalse && seenNil;
}
bool isApproximateTruthy(TypeId ty)
{
ty = follow(ty);
if (auto nt = get<NegationType>(ty))
return isApproximateFalsy(nt->ty);
return false;
}
bool isSimpleDiscriminant(TypeId ty)
{
ty = follow(ty);
return isApproximateTruthy(ty) || isApproximateFalsy(ty);
}
}
TypeFunctionReductionResult<TypeId> refineTypeFunction( TypeFunctionReductionResult<TypeId> refineTypeFunction(
TypeId instance, TypeId instance,
const std::vector<TypeId>& typeParams, const std::vector<TypeId>& typeParams,
@ -2181,8 +2260,12 @@ TypeFunctionReductionResult<TypeId> refineTypeFunction(
for (size_t i = 1; i < typeParams.size(); i++) for (size_t i = 1; i < typeParams.size(); i++)
discriminantTypes.push_back(follow(typeParams.at(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 // 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}, {}}; return {std::nullopt, Reduction::MaybeOk, {targetTy}, {}};
else else
{ {
@ -2192,6 +2275,18 @@ TypeFunctionReductionResult<TypeId> refineTypeFunction(
return {std::nullopt, Reduction::MaybeOk, {t}, {}}; 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. // Refine a target type and a discriminant one at a time.
// Returns result : TypeId, toBlockOn : vector<TypeId> // Returns result : TypeId, toBlockOn : vector<TypeId>
auto stepRefine = [&ctx](TypeId target, TypeId discriminant) -> std::pair<TypeId, std::vector<TypeId>> auto stepRefine = [&ctx](TypeId target, TypeId discriminant) -> std::pair<TypeId, std::vector<TypeId>>
@ -2221,8 +2316,6 @@ TypeFunctionReductionResult<TypeId> refineTypeFunction(
return {nullptr, {}}; return {nullptr, {}};
} }
else else
{
if (FFlag::LuauSearchForRefineableType)
{ {
// If the discriminant type is only: // If the discriminant type is only:
// - The `*no-refine*` type or, // - The `*no-refine*` type or,
@ -2232,18 +2325,6 @@ TypeFunctionReductionResult<TypeId> refineTypeFunction(
crt.traverse(discriminant); crt.traverse(discriminant);
if (!crt.found) if (!crt.found)
return {target, {}}; 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 (FFlag::LuauSimplyRefineNotNil) if (FFlag::LuauSimplyRefineNotNil)
{ {
@ -2257,6 +2338,49 @@ TypeFunctionReductionResult<TypeId> refineTypeFunction(
} }
} }
if (FFlag::LuauOptimizeFalsyAndTruthyIntersect)
{
// If the target type is a table, then simplification already implements the logic to deal with refinements properly since the
// type of the discriminant is guaranteed to only ever be an (arbitrarily-nested) table of a single property type.
// We also fire for simple discriminants such as false? and ~(false?): the falsy and truthy types respectively
// NOTE: It would be nice to be able to do a simple intersection for something like:
//
// { a: A, b: B, ... } & { x: X }
//
if (is<TableType>(target) || isSimpleDiscriminant(discriminant))
{
SimplifyResult result = simplifyIntersection(ctx->builtins, ctx->arena, target, discriminant);
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, {}};
}
}
else
{
// If the target type is a table, then simplification already implements the logic to deal with refinements properly since the // If the target type is a table, then simplification already implements the logic to deal with refinements properly since the
// type of the discriminant is guaranteed to only ever be an (arbitrarily-nested) table of a single property type. // type of the discriminant is guaranteed to only ever be an (arbitrarily-nested) table of a single property type.
if (get<TableType>(target)) if (get<TableType>(target))
@ -2267,6 +2391,8 @@ TypeFunctionReductionResult<TypeId> refineTypeFunction(
return {result.result, {}}; return {result.result, {}};
} }
}
// In the general case, we'll still use normalization though. // In the general case, we'll still use normalization though.
TypeId intersection = ctx->arena->addType(IntersectionType{{target, discriminant}}); TypeId intersection = ctx->arena->addType(IntersectionType{{target, discriminant}});
@ -2485,6 +2611,8 @@ TypeFunctionReductionResult<TypeId> intersectTypeFunction(
// fold over the types with `simplifyIntersection` // fold over the types with `simplifyIntersection`
TypeId resultTy = ctx->builtins->unknownType; TypeId resultTy = ctx->builtins->unknownType;
// collect types which caused intersection to return never
DenseHashSet<TypeId> unintersectableTypes{nullptr};
for (auto ty : types) for (auto ty : types)
{ {
// skip any `*no-refine*` types. // skip any `*no-refine*` types.
@ -2493,6 +2621,17 @@ TypeFunctionReductionResult<TypeId> intersectTypeFunction(
SimplifyResult result = simplifyIntersection(ctx->builtins, ctx->arena, resultTy, ty); SimplifyResult result = simplifyIntersection(ctx->builtins, ctx->arena, resultTy, ty);
if (FFlag::LuauNarrowIntersectionNevers)
{
// If simplifying the intersection returned never, note the type we tried to intersect it with, and continue trying to intersect with the
// rest
if (get<NeverType>(result.result))
{
unintersectableTypes.insert(follow(ty));
continue;
}
}
if (FFlag::LuauIntersectNotNil) if (FFlag::LuauIntersectNotNil)
{ {
for (TypeId blockedType : result.blockedTypes) for (TypeId blockedType : result.blockedTypes)
@ -2510,6 +2649,24 @@ TypeFunctionReductionResult<TypeId> intersectTypeFunction(
resultTy = result.result; resultTy = result.result;
} }
if (FFlag::LuauNarrowIntersectionNevers)
{
if (!unintersectableTypes.empty())
{
unintersectableTypes.insert(resultTy);
if (unintersectableTypes.size() > 1)
{
TypeId intersection =
ctx->arena->addType(IntersectionType{std::vector<TypeId>(unintersectableTypes.begin(), unintersectableTypes.end())});
return {intersection, Reduction::MaybeOk, {}, {}};
}
else
{
return {*unintersectableTypes.begin(), Reduction::MaybeOk, {}, {}};
}
}
}
// if the intersection simplifies to `never`, this gives us bad autocomplete. // if the intersection simplifies to `never`, this gives us bad autocomplete.
// we'll just produce the intersection plainly instead, but this might be revisitable // we'll just produce the intersection plainly instead, but this might be revisitable
// if we ever give `never` some kind of "explanation" trail. // if we ever give `never` some kind of "explanation" trail.
@ -2572,7 +2729,7 @@ bool computeKeysOf(TypeId ty, Set<std::string>& result, DenseHashSet<TypeId>& se
return res; return res;
} }
if (auto classTy = get<ClassType>(ty)) if (auto classTy = get<ExternType>(ty))
{ {
for (auto [key, _] : classTy->props) for (auto [key, _] : classTy->props)
result.insert(key); result.insert(key);
@ -2595,7 +2752,7 @@ bool computeKeysOf(TypeId ty, Set<std::string>& result, DenseHashSet<TypeId>& se
return res; 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); LUAU_ASSERT(false);
return false; return false;
} }
@ -2621,9 +2778,9 @@ TypeFunctionReductionResult<TypeId> keyofFunctionImpl(
if (!normTy) if (!normTy)
return {std::nullopt, Reduction::MaybeOk, {}, {}}; 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) // as well)
if (normTy->hasTables() == normTy->hasClasses()) if (normTy->hasTables() == normTy->hasExternTypes())
return {std::nullopt, Reduction::Erroneous, {}, {}}; 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. // 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.
@ -2634,31 +2791,31 @@ TypeFunctionReductionResult<TypeId> keyofFunctionImpl(
// we're going to collect the keys in here // we're going to collect the keys in here
Set<std::string> keys{{}}; Set<std::string> keys{{}};
// computing the keys for classes // computing the keys for extern types
if (normTy->hasClasses()) if (normTy->hasExternTypes())
{ {
LUAU_ASSERT(!normTy->hasTables()); LUAU_ASSERT(!normTy->hasTables());
// seen set for key computation for classes // seen set for key computation for extern types
DenseHashSet<TypeId> seen{{}}; DenseHashSet<TypeId> seen{{}};
auto classesIter = normTy->classes.ordering.begin(); auto externTypeIter = normTy->externTypes.ordering.begin();
auto classesIterEnd = normTy->classes.ordering.end(); auto externTypeIterEnd = normTy->externTypes.ordering.end();
LUAU_ASSERT(classesIter != classesIterEnd); // should be guaranteed by the `hasClasses` check earlier LUAU_ASSERT(externTypeIter != externTypeIterEnd); // should be guaranteed by the `hasExternTypes` check earlier
// collect all the properties from the first class type // 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! 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 // 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 seen.clear(); // we'll reuse the same seen set
Set<std::string> localKeys{{}}; Set<std::string> localKeys{{}};
// we can skip to the next class if this one is a top type // we can skip to the next class if this one is a top type
if (!computeKeysOf(*classesIter, localKeys, seen, isRaw, ctx)) if (!computeKeysOf(*externTypeIter, localKeys, seen, isRaw, ctx))
continue; continue;
for (auto& key : keys) for (auto& key : keys)
@ -2673,7 +2830,7 @@ TypeFunctionReductionResult<TypeId> keyofFunctionImpl(
// computing the keys for tables // computing the keys for tables
if (normTy->hasTables()) if (normTy->hasTables())
{ {
LUAU_ASSERT(!normTy->hasClasses()); LUAU_ASSERT(!normTy->hasExternTypes());
// seen set for key computation for tables // seen set for key computation for tables
DenseHashSet<TypeId> seen{{}}; DenseHashSet<TypeId> seen{{}};
@ -2835,7 +2992,7 @@ bool searchPropsAndIndexer(
return false; 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 `isRaw` parameter indicates whether or not we should follow __index metamethods
returns false if property of `ty` could not be found */ 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) bool tblIndexInto_DEPRECATED(TypeId indexer, TypeId indexee, DenseHashSet<TypeId>& result, NotNull<TypeFunctionContext> ctx, bool isRaw)
@ -3010,11 +3167,11 @@ TypeFunctionReductionResult<TypeId> indexFunctionImpl(
return {ctx->builtins->anyType, Reduction::MaybeOk, {}, {}}; 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 we don't have either just tables or just extern types, we've got nothing to index into
if (indexeeNormTy->hasTables() == indexeeNormTy->hasClasses()) if (indexeeNormTy->hasTables() == indexeeNormTy->hasExternTypes())
return {std::nullopt, Reduction::Erroneous, {}, {}}; 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() || if (indexeeNormTy->hasTops() || indexeeNormTy->hasBooleans() || indexeeNormTy->hasErrors() || indexeeNormTy->hasNils() ||
indexeeNormTy->hasNumbers() || indexeeNormTy->hasStrings() || indexeeNormTy->hasThreads() || indexeeNormTy->hasBuffers() || indexeeNormTy->hasNumbers() || indexeeNormTy->hasStrings() || indexeeNormTy->hasThreads() || indexeeNormTy->hasBuffers() ||
indexeeNormTy->hasFunctions() || indexeeNormTy->hasTyvars()) indexeeNormTy->hasFunctions() || indexeeNormTy->hasTyvars())
@ -3045,18 +3202,18 @@ TypeFunctionReductionResult<TypeId> indexFunctionImpl(
DenseHashSet<TypeId> properties{{}}; // vector of types that will be returned DenseHashSet<TypeId> properties{{}}; // vector of types that will be returned
if (indexeeNormTy->hasClasses()) if (indexeeNormTy->hasExternTypes())
{ {
LUAU_ASSERT(!indexeeNormTy->hasTables()); 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, {}, {}}; return {std::nullopt, Reduction::Erroneous, {}, {}};
// at least one class is guaranteed to be in the iterator by .hasClasses() // at least one class is guaranteed to be in the iterator by .hasExternTypes()
for (auto classesIter = indexeeNormTy->classes.ordering.begin(); classesIter != indexeeNormTy->classes.ordering.end(); ++classesIter) for (auto externTypeIter = indexeeNormTy->externTypes.ordering.begin(); externTypeIter != indexeeNormTy->externTypes.ordering.end(); ++externTypeIter)
{ {
auto classTy = get<ClassType>(*classesIter); auto externTy = get<ExternType>(*externTypeIter);
if (!classTy) if (!externTy)
{ {
LUAU_ASSERT(false); // this should not be possible according to normalization's spec LUAU_ASSERT(false); // this should not be possible according to normalization's spec
return {std::nullopt, Reduction::Erroneous, {}, {}}; return {std::nullopt, Reduction::Erroneous, {}, {}};
@ -3065,16 +3222,16 @@ TypeFunctionReductionResult<TypeId> indexFunctionImpl(
for (TypeId ty : *typesToFind) for (TypeId ty : *typesToFind)
{ {
// Search for all instances of indexer in class->props and class->indexer // 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 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; bool foundInParent = false;
while (parent && !foundInParent) while (parent && !foundInParent)
{ {
auto parentClass = get<ClassType>(follow(*parent)); auto parentExternType = get<ExternType>(follow(*parent));
foundInParent = searchPropsAndIndexer(ty, parentClass->props, parentClass->indexer, properties, ctx); foundInParent = searchPropsAndIndexer(ty, parentExternType->props, parentExternType->indexer, properties, ctx);
parent = parentClass->parent; parent = parentExternType->parent;
} }
// we move on to the next type if any of the parents we went through had the property. // we move on to the next type if any of the parents we went through had the property.
@ -3086,7 +3243,7 @@ TypeFunctionReductionResult<TypeId> indexFunctionImpl(
// findMetatableEntry demands the ability to emit errors, so we must give it // 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. // the necessary state to do that, even if we intend to just eat the errors.
ErrorVec dummy; 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 if (!mmType) // if a metatable does not exist, there is no where else to look
return {std::nullopt, Reduction::Erroneous, {}, {}}; return {std::nullopt, Reduction::Erroneous, {}, {}};
@ -3098,7 +3255,7 @@ TypeFunctionReductionResult<TypeId> indexFunctionImpl(
if (indexeeNormTy->hasTables()) if (indexeeNormTy->hasTables())
{ {
LUAU_ASSERT(!indexeeNormTy->hasClasses()); LUAU_ASSERT(!indexeeNormTy->hasExternTypes());
// at least one table is guaranteed to be in the iterator by .hasTables() // at least one table is guaranteed to be in the iterator by .hasTables()
for (auto tablesIter = indexeeNormTy->tables.begin(); tablesIter != indexeeNormTy->tables.end(); ++tablesIter) for (auto tablesIter = indexeeNormTy->tables.begin(); tablesIter != indexeeNormTy->tables.end(); ++tablesIter)
@ -3193,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. // 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() || if (targetNorm->hasTops() || targetNorm->hasBooleans() || targetNorm->hasErrors() || targetNorm->hasNils() || targetNorm->hasNumbers() ||
targetNorm->hasStrings() || targetNorm->hasThreads() || targetNorm->hasBuffers() || targetNorm->hasFunctions() || targetNorm->hasTyvars() || targetNorm->hasStrings() || targetNorm->hasThreads() || targetNorm->hasBuffers() || targetNorm->hasFunctions() || targetNorm->hasTyvars() ||
targetNorm->hasClasses()) targetNorm->hasExternTypes())
return {std::nullopt, Reduction::Erroneous, {}, {}}; return {std::nullopt, Reduction::Erroneous, {}, {}};
// if the supposed metatable is not a table, we will fail to reduce. // if the supposed metatable is not a table, we will fail to reduce.
@ -3267,7 +3424,7 @@ static TypeFunctionReductionResult<TypeId> getmetatableHelper(TypeId targetTy, c
erroneous = false; erroneous = false;
} }
if (auto clazz = get<ClassType>(targetTy)) if (auto clazz = get<ExternType>(targetTy))
{ {
metatable = clazz->metatable; metatable = clazz->metatable;
erroneous = false; erroneous = false;
@ -3413,12 +3570,12 @@ BuiltinTypeFunctions::BuiltinTypeFunctions()
, powFunc{"pow", powTypeFunction} , powFunc{"pow", powTypeFunction}
, modFunc{"mod", modTypeFunction} , modFunc{"mod", modTypeFunction}
, concatFunc{"concat", concatTypeFunction} , concatFunc{"concat", concatTypeFunction}
, andFunc{"and", andTypeFunction} , andFunc{"and", andTypeFunction, /*canReduceGenerics*/ true}
, orFunc{"or", orTypeFunction} , orFunc{"or", orTypeFunction, /*canReduceGenerics*/ true}
, ltFunc{"lt", ltTypeFunction} , ltFunc{"lt", ltTypeFunction}
, leFunc{"le", leTypeFunction} , leFunc{"le", leTypeFunction}
, eqFunc{"eq", eqTypeFunction} , eqFunc{"eq", eqTypeFunction}
, refineFunc{"refine", refineTypeFunction} , refineFunc{"refine", refineTypeFunction, /*canReduceGenerics*/ FFlag::DebugLuauGreedyGeneralization}
, singletonFunc{"singleton", singletonTypeFunction} , singletonFunc{"singleton", singletonTypeFunction}
, unionFunc{"union", unionTypeFunction} , unionFunc{"union", unionTypeFunction}
, intersectFunc{"intersect", intersectTypeFunction} , intersectFunc{"intersect", intersectTypeFunction}

View file

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

View file

@ -15,6 +15,7 @@
LUAU_DYNAMIC_FASTINT(LuauTypeFunctionSerdeIterationLimit) LUAU_DYNAMIC_FASTINT(LuauTypeFunctionSerdeIterationLimit)
LUAU_FASTFLAGVARIABLE(LuauTypeFunReadWriteParents) LUAU_FASTFLAGVARIABLE(LuauTypeFunReadWriteParents)
LUAU_FASTFLAGVARIABLE(LuauTypeFunOptional)
namespace Luau namespace Luau
{ {
@ -154,7 +155,7 @@ static std::string getTag(lua_State* L, TypeFunctionTypeId ty)
return "table"; return "table";
else if (get<TypeFunctionFunctionType>(ty)) else if (get<TypeFunctionFunctionType>(ty))
return "function"; return "function";
else if (get<TypeFunctionClassType>(ty)) else if (get<TypeFunctionExternType>(ty))
return "class"; return "class";
else if (get<TypeFunctionGenericType>(ty)) else if (get<TypeFunctionGenericType>(ty))
return "generic"; 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()); 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` // Luau: `types.unionof(...: type) -> type`
// Returns the type instance representing union // Returns the type instance representing union
static int createUnion(lua_State* L) 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); luaL_error(L, "type.parent: expected 1 arguments, but got %d", argumentCount);
TypeFunctionTypeId self = getTypeUserData(L, 1); TypeFunctionTypeId self = getTypeUserData(L, 1);
auto tfct = get<TypeFunctionClassType>(self); auto tfct = get<TypeFunctionExternType>(self);
if (!tfct) if (!tfct)
luaL_error(L, "type.parent: expected self to be a class, but got %s instead", getTag(L, self).c_str()); 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); luaL_error(L, "type.parent: expected 1 arguments, but got %d", argumentCount);
TypeFunctionTypeId self = getTypeUserData(L, 1); TypeFunctionTypeId self = getTypeUserData(L, 1);
auto tfct = get<TypeFunctionClassType>(self); auto tfct = get<TypeFunctionExternType>(self);
if (!tfct) if (!tfct)
luaL_error(L, "type.parent: expected self to be a class, but got %s instead", getTag(L, self).c_str()); 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); luaL_error(L, "type.parent: expected 1 arguments, but got %d", argumentCount);
TypeFunctionTypeId self = getTypeUserData(L, 1); TypeFunctionTypeId self = getTypeUserData(L, 1);
auto tfct = get<TypeFunctionClassType>(self); auto tfct = get<TypeFunctionExternType>(self);
if (!tfct) if (!tfct)
luaL_error(L, "type.parent: expected self to be a class, but got %s instead", getTag(L, self).c_str()); 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; return 1;
} }
if (auto tfct = get<TypeFunctionClassType>(self)) if (auto tfct = get<TypeFunctionExternType>(self))
{ {
lua_createtable(L, int(tfct->props.size()), 0); lua_createtable(L, int(tfct->props.size()), 0);
for (auto& [name, prop] : tfct->props) for (auto& [name, prop] : tfct->props)
@ -1305,7 +1338,7 @@ static int getIndexer(lua_State* L)
return 1; 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 the indexer does not exist, we should return nil
if (!tfct->indexer.has_value()) if (!tfct->indexer.has_value())
@ -1353,7 +1386,7 @@ static int getReadIndexer(lua_State* L)
return 1; 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 the indexer does not exist, we should return nil
if (!tfct->indexer.has_value()) if (!tfct->indexer.has_value())
@ -1399,7 +1432,7 @@ static int getWriteIndexer(lua_State* L)
return 1; 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 the indexer does not exist, we should return nil
if (!tfct->indexer.has_value()) if (!tfct->indexer.has_value())
@ -1439,7 +1472,7 @@ static int getMetatable(lua_State* L)
return 1; 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 the metatable does not exist, we should return nil
if (!tfct->metatable.has_value()) if (!tfct->metatable.has_value())
@ -1524,6 +1557,7 @@ void registerTypesLibrary(lua_State* L)
{"copy", deepCopy}, {"copy", deepCopy},
{"generic", createGeneric}, {"generic", createGeneric},
{(FFlag::LuauTypeFunOptional) ? "optional" : nullptr, (FFlag::LuauTypeFunOptional) ? createOptional : nullptr},
{nullptr, nullptr} {nullptr, nullptr}
}; };
@ -1593,7 +1627,7 @@ void registerTypeUserData(lua_State* L)
// Union and Intersection type methods // Union and Intersection type methods
{"components", getComponents}, {"components", getComponents},
// Class type methods // Extern type methods
{FFlag::LuauTypeFunReadWriteParents ? "readparent" : "parent", FFlag::LuauTypeFunReadWriteParents ? getReadParent : getClassParent_DEPRECATED}, {FFlag::LuauTypeFunReadWriteParents ? "readparent" : "parent", FFlag::LuauTypeFunReadWriteParents ? getReadParent : getClassParent_DEPRECATED},
// Function type methods (cont.) // Function type methods (cont.)
@ -1604,7 +1638,7 @@ void registerTypeUserData(lua_State* L)
{"name", getGenericName}, {"name", getGenericName},
{"ispack", getGenericIsPack}, {"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}, {FFlag::LuauTypeFunReadWriteParents ? "writeparent" : nullptr, FFlag::LuauTypeFunReadWriteParents ? getWriteParent : nullptr},
{nullptr, nullptr} {nullptr, nullptr}
@ -1903,12 +1937,12 @@ bool areEqual(SeenSet& seen, const TypeFunctionFunctionType& lhs, const TypeFunc
return true; 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)) if (seenSetContains(seen, &lhs, &rhs))
return true; return true;
return lhs.classTy == rhs.classTy; return lhs.externTy == rhs.externTy;
} }
bool areEqual(SeenSet& seen, const TypeFunctionType& lhs, const TypeFunctionType& rhs) 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 TypeFunctionExternType* lf = get<TypeFunctionExternType>(&lhs);
const TypeFunctionClassType* rf = get<TypeFunctionClassType>(&rhs); const TypeFunctionExternType* rf = get<TypeFunctionExternType>(&rhs);
if (lf && rf) if (lf && rf)
return areEqual(seen, *lf, *rf); return areEqual(seen, *lf, *rf);
} }
@ -2266,7 +2300,7 @@ private:
TypeFunctionTypePackId emptyTypePack = typeFunctionRuntime->typePackArena.allocate(TypeFunctionTypePack{}); TypeFunctionTypePackId emptyTypePack = typeFunctionRuntime->typePackArena.allocate(TypeFunctionTypePack{});
target = typeFunctionRuntime->typeArena.allocate(TypeFunctionFunctionType{{}, {}, emptyTypePack, emptyTypePack}); 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 target = ty; // Don't copy a class since they are immutable
else if (auto g = get<TypeFunctionGenericType>(ty)) else if (auto g = get<TypeFunctionGenericType>(ty))
target = typeFunctionRuntime->typeArena.allocate(TypeFunctionGenericType{g->isNamed, g->isPack, g->name}); target = typeFunctionRuntime->typeArena.allocate(TypeFunctionGenericType{g->isNamed, g->isPack, g->name});
@ -2321,7 +2355,7 @@ private:
cloneChildren(t1, t2); cloneChildren(t1, t2);
else if (auto [f1, f2] = std::tuple{getMutable<TypeFunctionFunctionType>(ty), getMutable<TypeFunctionFunctionType>(tfti)}; f1 && f2) else if (auto [f1, f2] = std::tuple{getMutable<TypeFunctionFunctionType>(ty), getMutable<TypeFunctionFunctionType>(tfti)}; f1 && f2)
cloneChildren(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); cloneChildren(c1, c2);
else if (auto [g1, g2] = std::tuple{getMutable<TypeFunctionGenericType>(ty), getMutable<TypeFunctionGenericType>(tfti)}; g1 && g2) else if (auto [g1, g2] = std::tuple{getMutable<TypeFunctionGenericType>(ty), getMutable<TypeFunctionGenericType>(tfti)}; g1 && g2)
cloneChildren(g1, g2); cloneChildren(g1, g2);
@ -2431,7 +2465,7 @@ private:
f2->retTypes = shallowClone(f1->retTypes); f2->retTypes = shallowClone(f1->retTypes);
} }
void cloneChildren(TypeFunctionClassType* c1, TypeFunctionClassType* c2) void cloneChildren(TypeFunctionExternType* c1, TypeFunctionExternType* c2)
{ {
// noop. // noop.
} }

View file

@ -206,12 +206,12 @@ private:
TypeFunctionTypePackId emptyTypePack = typeFunctionRuntime->typePackArena.allocate(TypeFunctionTypePack{}); TypeFunctionTypePackId emptyTypePack = typeFunctionRuntime->typePackArena.allocate(TypeFunctionTypePack{});
target = typeFunctionRuntime->typeArena.allocate(TypeFunctionFunctionType{{}, {}, emptyTypePack, emptyTypePack}); 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 // 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 // class
target = typeFunctionRuntime->typeArena.allocate( 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)) else if (auto g = get<GenericType>(ty))
@ -291,7 +291,7 @@ private:
serializeChildren(m1, m2); serializeChildren(m1, m2);
else if (auto [f1, f2] = std::tuple{get<FunctionType>(ty), getMutable<TypeFunctionFunctionType>(tfti)}; f1 && f2) else if (auto [f1, f2] = std::tuple{get<FunctionType>(ty), getMutable<TypeFunctionFunctionType>(tfti)}; f1 && f2)
serializeChildren(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); serializeChildren(c1, c2);
else if (auto [g1, g2] = std::tuple{get<GenericType>(ty), getMutable<TypeFunctionGenericType>(tfti)}; g1 && g2) else if (auto [g1, g2] = std::tuple{get<GenericType>(ty), getMutable<TypeFunctionGenericType>(tfti)}; g1 && g2)
serializeChildren(g1, g2); serializeChildren(g1, g2);
@ -411,7 +411,7 @@ private:
f2->retTypes = shallowSerialize(f1->retTypes); 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) for (const auto& [k, p] : c1->props)
{ {
@ -702,9 +702,9 @@ private:
TypePackId emptyTypePack = state->ctx->arena->addTypePack(TypePack{}); TypePackId emptyTypePack = state->ctx->arena->addTypePack(TypePack{});
target = state->ctx->arena->addType(FunctionType{emptyTypePack, emptyTypePack, {}, false}); 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)) else if (auto g = get<TypeFunctionGenericType>(ty))
{ {
@ -811,7 +811,7 @@ private:
deserializeChildren(m2, m1); deserializeChildren(m2, m1);
else if (auto [f1, f2] = std::tuple{getMutable<FunctionType>(ty), getMutable<TypeFunctionFunctionType>(tfti)}; f1 && f2) else if (auto [f1, f2] = std::tuple{getMutable<FunctionType>(ty), getMutable<TypeFunctionFunctionType>(tfti)}; f1 && f2)
deserializeChildren(f2, f1); 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); deserializeChildren(c2, c1);
else if (auto [g1, g2] = std::tuple{getMutable<GenericType>(ty), getMutable<TypeFunctionGenericType>(tfti)}; g1 && g2) else if (auto [g1, g2] = std::tuple{getMutable<GenericType>(ty), getMutable<TypeFunctionGenericType>(tfti)}; g1 && g2)
deserializeChildren(g2, g1); deserializeChildren(g2, g1);
@ -972,7 +972,7 @@ private:
f1->retTypes = shallowDeserialize(f2->retTypes); f1->retTypes = shallowDeserialize(f2->retTypes);
} }
void deserializeChildren(TypeFunctionClassType* c2, ClassType* c1) void deserializeChildren(TypeFunctionExternType* c2, ExternType* c1)
{ {
// noop. // noop.
} }

View file

@ -32,17 +32,19 @@ LUAU_FASTINTVARIABLE(LuauVisitRecursionLimit, 500)
LUAU_FASTFLAG(LuauKnowsTheDataModel3) LUAU_FASTFLAG(LuauKnowsTheDataModel3)
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification) LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification)
LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAG(LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType) LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
LUAU_FASTFLAG(LuauRetainDefinitionAliasLocations) LUAU_FASTFLAG(LuauRetainDefinitionAliasLocations)
LUAU_FASTFLAGVARIABLE(LuauStatForInFix) LUAU_FASTFLAGVARIABLE(LuauStatForInFix)
LUAU_FASTFLAGVARIABLE(LuauReduceCheckBinaryExprStackPressure)
LUAU_FASTFLAGVARIABLE(LuauLimitIterationWhenCheckingArgumentCounts)
namespace Luau namespace Luau
{ {
static bool typeCouldHaveMetatable(TypeId ty) 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) static void defaultLuauPrintLine(const std::string& s)
@ -316,7 +318,7 @@ ModulePtr TypeChecker::checkWithoutRecursionCheck(const SourceModule& module, Mo
unifierState.skipCacheForType.clear(); unifierState.skipCacheForType.clear();
duplicateTypeAliases.clear(); duplicateTypeAliases.clear();
incorrectClassDefinitions.clear(); incorrectExternTypeDefinitions.clear();
return std::move(currentModule); return std::move(currentModule);
} }
@ -381,7 +383,7 @@ ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStat& program)
} }
else if (auto global = program.as<AstStatDeclareFunction>()) else if (auto global = program.as<AstStatDeclareFunction>())
return check(scope, *global); return check(scope, *global);
else if (auto global = program.as<AstStatDeclareClass>()) else if (auto global = program.as<AstStatDeclareExternType>())
return check(scope, *global); return check(scope, *global);
else if (auto errorStatement = program.as<AstStatError>()) else if (auto errorStatement = program.as<AstStatError>())
{ {
@ -496,9 +498,9 @@ ControlFlow TypeChecker::checkBlockWithoutRecursionCheck(const ScopePtr& scope,
prototype(scope, *typealias, subLevel); prototype(scope, *typealias, subLevel);
++subLevel; ++subLevel;
} }
else if (const auto& declaredClass = stat->as<AstStatDeclareClass>()) else if (const auto& declaredExternType = stat->as<AstStatDeclareExternType>())
{ {
prototype(scope, *declaredClass); prototype(scope, *declaredExternType);
} }
} }
@ -786,7 +788,7 @@ struct Demoter : Substitution
bool ignoreChildren(TypeId ty) override bool ignoreChildren(TypeId ty) override
{ {
if (get<ClassType>(ty)) if (get<ExternType>(ty))
return true; return true;
return false; return false;
@ -796,8 +798,7 @@ struct Demoter : Substitution
{ {
auto ftv = get<FreeType>(ty); auto ftv = get<FreeType>(ty);
LUAU_ASSERT(ftv); LUAU_ASSERT(ftv);
return FFlag::LuauFreeTypesMustHaveBounds ? arena->freshType(builtins, demotedLevel(ftv->level)) return arena->freshType(builtins, demotedLevel(ftv->level));
: addType(FreeType{demotedLevel(ftv->level)});
} }
TypePackId clean(TypePackId tp) override TypePackId clean(TypePackId tp) override
@ -1684,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); std::optional<TypeId> superTy = std::make_optional(builtinTypes->externType);
if (declaredClass.superName) if (declaredExternType.superName)
{ {
Name superName = Name(declaredClass.superName->value); Name superName = Name(declaredExternType.superName->value);
std::optional<TypeFun> lookupType = scope->lookupType(superName); std::optional<TypeFun> lookupType = scope->lookupType(superName);
if (!lookupType) if (!lookupType)
{ {
reportError(declaredClass.location, UnknownSymbol{superName, UnknownSymbol::Type}); reportError(declaredExternType.location, UnknownSymbol{superName, UnknownSymbol::Type});
incorrectClassDefinitions.insert(&declaredClass); incorrectExternTypeDefinitions.insert(&declaredExternType);
return; 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); LUAU_ASSERT(lookupType->typeParams.size() == 0 && lookupType->typePackParams.size() == 0);
superTy = lookupType->type; superTy = lookupType->type;
if (!get<ClassType>(follow(*superTy))) if (!get<ExternType>(follow(*superTy)))
{ {
reportError( reportError(
declaredClass.location, declaredExternType.location,
GenericError{format("Cannot use non-class type '%s' as a superclass of class '%s'", superName.c_str(), declaredClass.name.value)} 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; return;
} }
} }
Name className(declaredClass.name.value); Name className(declaredExternType.name.value);
TypeId classTy = addType(ClassType(className, {}, superTy, std::nullopt, {}, {}, currentModule->name, declaredClass.location)); TypeId classTy = addType(ExternType(className, {}, superTy, std::nullopt, {}, {}, currentModule->name, declaredExternType.location));
ClassType* ctv = getMutable<ClassType>(classTy); ExternType* etv = getMutable<ExternType>(classTy);
TypeId metaTy = addType(TableType{TableState::Sealed, scope->level}); TypeId metaTy = addType(TableType{TableState::Sealed, scope->level});
ctv->metatable = metaTy; etv->metatable = metaTy;
if (FFlag::LuauRetainDefinitionAliasLocations) if (FFlag::LuauRetainDefinitionAliasLocations)
scope->exportedTypeBindings[className] = TypeFun{{}, classTy, declaredClass.location}; scope->exportedTypeBindings[className] = TypeFun{{}, classTy, declaredExternType.location};
else else
scope->exportedTypeBindings[className] = TypeFun{{}, classTy}; 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 // Don't bother checking if the class definition was incorrect
if (incorrectClassDefinitions.find(&declaredClass)) if (incorrectExternTypeDefinitions.find(&declaredExternType))
return ControlFlow::None; return ControlFlow::None;
std::optional<TypeFun> binding; std::optional<TypeFun> binding;
if (auto it = scope->exportedTypeBindings.find(className); it != scope->exportedTypeBindings.end()) if (auto it = scope->exportedTypeBindings.find(className); it != scope->exportedTypeBindings.end())
binding = it->second; binding = it->second;
// This class definition must have been `prototype()`d first. // This extern type definition must have been `prototype()`d first.
if (!binding) if (!binding)
ice("Class not predeclared"); ice("Extern type not predeclared");
TypeId classTy = binding->type; TypeId externTy = binding->type;
ClassType* ctv = getMutable<ClassType>(classTy); ExternType* etv = getMutable<ExternType>(externTy);
if (!ctv->metatable) if (!etv->metatable)
ice("No metatable for declared class"); ice("No metatable for declared extern type");
if (const auto& indexer = declaredClass.indexer) if (const auto& indexer = declaredExternType.indexer)
ctv->indexer = TableIndexer(resolveType(scope, *indexer->indexType), resolveType(scope, *indexer->resultType)); etv->indexer = TableIndexer(resolveType(scope, *indexer->indexType), resolveType(scope, *indexer->resultType));
TableType* metatable = getMutable<TableType>(*ctv->metatable); TableType* metatable = getMutable<TableType>(*etv->metatable);
for (const AstDeclaredClassProp& prop : declaredClass.props) for (const AstDeclaredExternTypeProperty& prop : declaredExternType.props)
{ {
Name propName(prop.name.value); Name propName(prop.name.value);
TypeId propTy = resolveType(scope, *prop.ty); TypeId propTy = resolveType(scope, *prop.ty);
bool assignToMetatable = isMetamethod(propName); 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 // Function types always take 'self', but this isn't reflected in the
// parsed annotation. Add it here. // parsed annotation. Add it here.
@ -1768,7 +1769,7 @@ ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatDeclareClass&
if (FunctionType* ftv = getMutable<FunctionType>(propTy)) if (FunctionType* ftv = getMutable<FunctionType>(propTy))
{ {
ftv->argNames.insert(ftv->argNames.begin(), FunctionArgument{"self", {}}); 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; ftv->hasSelf = true;
FunctionDefinition defn; FunctionDefinition defn;
@ -1811,7 +1812,7 @@ ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatDeclareClass&
} }
else 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())});
} }
} }
} }
@ -1850,7 +1851,8 @@ ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatDeclareFuncti
); );
TypePackId argPack = resolveTypePack(funScope, global.params); 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; FunctionDefinition defn;
@ -1924,7 +1926,7 @@ WithPredicate<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExp
else if (auto a = expr.as<AstExprUnary>()) else if (auto a = expr.as<AstExprUnary>())
result = checkExpr(scope, *a); result = checkExpr(scope, *a);
else if (auto a = expr.as<AstExprBinary>()) else if (auto a = expr.as<AstExprBinary>())
result = checkExpr(scope, *a, expectedType); result = FFlag::LuauReduceCheckBinaryExprStackPressure ? checkExpr(scope, *a, expectedType) : checkExpr_DEPRECATED(scope, *a, expectedType);
else if (auto a = expr.as<AstExprTypeAssertion>()) else if (auto a = expr.as<AstExprTypeAssertion>())
result = checkExpr(scope, *a); result = checkExpr(scope, *a);
else if (auto a = expr.as<AstExprError>()) else if (auto a = expr.as<AstExprError>())
@ -2135,9 +2137,9 @@ std::optional<TypeId> TypeChecker::getIndexTypeFromTypeImpl(
if (auto found = findTablePropertyRespectingMeta(type, name, location, addErrors)) if (auto found = findTablePropertyRespectingMeta(type, name, location, addErrors))
return *found; 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) if (prop)
return prop->type(); return prop->type();
@ -3186,6 +3188,9 @@ WithPredicate<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExp
return {result, {OrPredicate{std::move(lhsPredicates), std::move(rhsPredicates)}}}; return {result, {OrPredicate{std::move(lhsPredicates), std::move(rhsPredicates)}}};
} }
else if (expr.op == AstExprBinary::CompareEq || expr.op == AstExprBinary::CompareNe) else if (expr.op == AstExprBinary::CompareEq || expr.op == AstExprBinary::CompareNe)
{
// Defer the stack allocation of lhs, predicate etc until this lambda is called.
auto checkExprOr = [&]() -> WithPredicate<TypeId>
{ {
// For these, passing expectedType is worse than simply forcing them, because their implementation // For these, passing expectedType is worse than simply forcing them, because their implementation
// may inadvertently check if expectedTypes exist first and use it, instead of forceSingleton first. // may inadvertently check if expectedTypes exist first and use it, instead of forceSingleton first.
@ -3198,8 +3203,67 @@ WithPredicate<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExp
PredicateVec predicates; PredicateVec predicates;
if (auto lvalue = tryGetLValue(*expr.left)) if (auto lvalue = tryGetLValue(*expr.left))
predicates.push_back(EqPredicate{std::move(*lvalue), rhs.type, expr.location}); predicates.emplace_back(EqPredicate{std::move(*lvalue), rhs.type, expr.location});
if (auto lvalue = tryGetLValue(*expr.right))
predicates.emplace_back(EqPredicate{std::move(*lvalue), lhs.type, expr.location});
if (!predicates.empty() && expr.op == AstExprBinary::CompareNe)
predicates = {NotPredicate{std::move(predicates)}};
return {checkBinaryOperation(scope, expr, lhs.type, rhs.type), std::move(predicates)};
};
return checkExprOr();
}
else
{
// Expected types are not useful for other binary operators.
WithPredicate<TypeId> lhs = checkExpr(scope, *expr.left);
WithPredicate<TypeId> rhs = checkExpr(scope, *expr.right);
// Intentionally discarding predicates with other operators.
return WithPredicate{checkBinaryOperation(scope, expr, lhs.type, rhs.type, lhs.predicates)};
}
}
WithPredicate<TypeId> TypeChecker::checkExpr_DEPRECATED(const ScopePtr& scope, const AstExprBinary& expr, std::optional<TypeId> expectedType)
{
if (expr.op == AstExprBinary::And)
{
auto [lhsTy, lhsPredicates] = checkExpr(scope, *expr.left, expectedType);
ScopePtr innerScope = childScope(scope, expr.location);
resolve(lhsPredicates, innerScope, true);
auto [rhsTy, rhsPredicates] = checkExpr(innerScope, *expr.right, expectedType);
return {checkBinaryOperation(scope, expr, lhsTy, rhsTy), {AndPredicate{std::move(lhsPredicates), std::move(rhsPredicates)}}};
}
else if (expr.op == AstExprBinary::Or)
{
auto [lhsTy, lhsPredicates] = checkExpr(scope, *expr.left, expectedType);
ScopePtr innerScope = childScope(scope, expr.location);
resolve(lhsPredicates, innerScope, false);
auto [rhsTy, rhsPredicates] = checkExpr(innerScope, *expr.right, expectedType);
// Because of C++, I'm not sure if lhsPredicates was not moved out by the time we call checkBinaryOperation.
TypeId result = checkBinaryOperation(scope, expr, lhsTy, rhsTy, lhsPredicates);
return {result, {OrPredicate{std::move(lhsPredicates), std::move(rhsPredicates)}}};
}
else if (expr.op == AstExprBinary::CompareEq || expr.op == AstExprBinary::CompareNe)
{
// For these, passing expectedType is worse than simply forcing them, because their implementation
// may inadvertently check if expectedTypes exist first and use it, instead of forceSingleton first.
WithPredicate<TypeId> lhs = checkExpr(scope, *expr.left, std::nullopt, /*forceSingleton=*/true);
WithPredicate<TypeId> rhs = checkExpr(scope, *expr.right, std::nullopt, /*forceSingleton=*/true);
if (auto predicate = tryGetTypeGuardPredicate(expr))
return {booleanType, {std::move(*predicate)}};
PredicateVec predicates;
if (auto lvalue = tryGetLValue(*expr.left))
predicates.push_back(EqPredicate{std::move(*lvalue), rhs.type, expr.location});
if (auto lvalue = tryGetLValue(*expr.right)) if (auto lvalue = tryGetLValue(*expr.right))
predicates.push_back(EqPredicate{std::move(*lvalue), lhs.type, expr.location}); predicates.push_back(EqPredicate{std::move(*lvalue), lhs.type, expr.location});
@ -3398,14 +3462,14 @@ TypeId TypeChecker::checkLValueBinding(const ScopePtr& scope, const AstExprIndex
return errorRecoveryType(scope); 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(); return prop->type();
} }
if (auto indexer = lhsClass->indexer) if (auto indexer = lhsExternType->indexer)
{ {
Unifier state = mkUnifier(scope, expr.location); Unifier state = mkUnifier(scope, expr.location);
state.tryUnify(stringType, indexer->indexType); state.tryUnify(stringType, indexer->indexType);
@ -3457,14 +3521,14 @@ TypeId TypeChecker::checkLValueBinding(const ScopePtr& scope, const AstExprIndex
if (value) 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(); return prop->type();
} }
if (auto indexer = exprClass->indexer) if (auto indexer = exprExternType->indexer)
{ {
unify(stringType, indexer->indexType, scope, expr.index->location); unify(stringType, indexer->indexType, scope, expr.index->location);
return indexer->indexResultType; return indexer->indexResultType;
@ -3490,20 +3554,20 @@ TypeId TypeChecker::checkLValueBinding(const ScopePtr& scope, const AstExprIndex
} }
else 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); unify(indexType, indexer->indexType, scope, expr.index->location);
return indexer->indexResultType; return indexer->indexResultType;
} }
} }
if (const ClassType* exprClass = get<ClassType>(exprType)) if (const ExternType* exprExternType = get<ExternType>(exprType))
{ {
if (isNonstrictMode()) if (isNonstrictMode())
return unknownType; return unknownType;
reportError(TypeError{expr.location, DynamicPropertyLookupOnClassesUnsafe{exprType}}); reportError(TypeError{expr.location, DynamicPropertyLookupOnExternTypesUnsafe{exprType}});
return errorRecoveryType(scope); return errorRecoveryType(scope);
} }
} }
@ -3784,8 +3848,10 @@ std::pair<TypeId, ScopePtr> TypeChecker::checkFunctionSignature(
auto [generics, genericPacks] = createGenericTypes(funScope, std::nullopt, expr, expr.generics, expr.genericPacks); auto [generics, genericPacks] = createGenericTypes(funScope, std::nullopt, expr, expr.generics, expr.genericPacks);
TypePackId retPack; TypePackId retPack;
if (expr.returnAnnotation) if (FFlag::LuauStoreReturnTypesAsPackOnAst && expr.returnAnnotation)
retPack = resolveTypePack(funScope, *expr.returnAnnotation); retPack = resolveTypePack(funScope, *expr.returnAnnotation);
else if (!FFlag::LuauStoreReturnTypesAsPackOnAst && expr.returnAnnotation_DEPRECATED)
retPack = resolveTypePack(funScope, *expr.returnAnnotation_DEPRECATED);
else if (isNonstrictMode()) else if (isNonstrictMode())
retPack = anyTypePack; retPack = anyTypePack;
else if (expectedFunctionType && expectedFunctionType->generics.empty() && expectedFunctionType->genericPacks.empty()) else if (expectedFunctionType && expectedFunctionType->generics.empty() && expectedFunctionType->genericPacks.empty())
@ -3992,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 // If we're in nonstrict mode we want to only report this missing return
// statement if there are type annotations on the function. In strict mode // statement if there are type annotations on the function. In strict mode
// we report it regardless. // we report it regardless.
if (!isNonstrictMode() || function.returnAnnotation) if (!isNonstrictMode() ||
(FFlag::LuauStoreReturnTypesAsPackOnAst ? function.returnAnnotation != nullptr : function.returnAnnotation_DEPRECATED.has_value()))
{ {
reportError(getEndLocation(function), FunctionExitsWithoutReturning{funTy->retTypes}); reportError(getEndLocation(function), FunctionExitsWithoutReturning{funTy->retTypes});
} }
@ -4050,6 +4117,23 @@ void TypeChecker::checkArgumentList(
size_t paramIndex = 0; size_t paramIndex = 0;
int loopCount = 0;
auto exceedsLoopCount = [&]()
{
if (FFlag::LuauLimitIterationWhenCheckingArgumentCounts)
{
++loopCount;
if (loopCount > FInt::LuauTypeInferTypePackLoopLimit)
{
state.reportError(TypeError{state.location, CodeTooComplex{}});
reportErrorCodeTooComplex(state.location);
return true;
}
}
return false;
};
auto reportCountMismatchError = [&state, &argLocations, paramPack, argPack, &funName]() auto reportCountMismatchError = [&state, &argLocations, paramPack, argPack, &funName]()
{ {
// For this case, we want the error span to cover every errant extra parameter // For this case, we want the error span to cover every errant extra parameter
@ -4124,12 +4208,17 @@ void TypeChecker::checkArgumentList(
} }
else if (auto vtp = state.log.getMutable<VariadicTypePack>(tail)) else if (auto vtp = state.log.getMutable<VariadicTypePack>(tail))
{ {
loopCount = 0;
// Function is variadic and requires that all subsequent parameters // Function is variadic and requires that all subsequent parameters
// be compatible with a type. // be compatible with a type.
while (paramIter != endIter) while (paramIter != endIter)
{ {
state.tryUnify(vtp->ty, *paramIter); state.tryUnify(vtp->ty, *paramIter);
++paramIter; ++paramIter;
if (exceedsLoopCount())
return;
} }
return; return;
@ -4138,10 +4227,16 @@ void TypeChecker::checkArgumentList(
{ {
std::vector<TypeId> rest; std::vector<TypeId> rest;
rest.reserve(std::distance(paramIter, endIter)); rest.reserve(std::distance(paramIter, endIter));
loopCount = 0;
while (paramIter != endIter) while (paramIter != endIter)
{ {
rest.push_back(*paramIter); rest.push_back(*paramIter);
++paramIter; ++paramIter;
if (exceedsLoopCount())
return;
} }
TypePackId varPack = addTypePack(TypePackVar{TypePack{rest, paramIter.tail()}}); TypePackId varPack = addTypePack(TypePackVar{TypePack{rest, paramIter.tail()}});
@ -4185,12 +4280,17 @@ void TypeChecker::checkArgumentList(
// too many parameters passed // too many parameters passed
if (!paramIter.tail()) if (!paramIter.tail())
{ {
loopCount = 0;
while (argIter != endIter) while (argIter != endIter)
{ {
// The use of unify here is deliberate. We don't want this unification // The use of unify here is deliberate. We don't want this unification
// to be undoable. // to be undoable.
unify(errorRecoveryType(scope), *argIter, scope, state.location); unify(errorRecoveryType(scope), *argIter, scope, state.location);
++argIter; ++argIter;
if (exceedsLoopCount())
return;
} }
reportCountMismatchError(); reportCountMismatchError();
return; return;
@ -4204,6 +4304,8 @@ void TypeChecker::checkArgumentList(
} }
else if (auto vtp = state.log.getMutable<VariadicTypePack>(tail)) else if (auto vtp = state.log.getMutable<VariadicTypePack>(tail))
{ {
loopCount = 0;
// Function is variadic and requires that all subsequent parameters // Function is variadic and requires that all subsequent parameters
// be compatible with a type. // be compatible with a type.
size_t argIndex = paramIndex; size_t argIndex = paramIndex;
@ -4219,12 +4321,17 @@ void TypeChecker::checkArgumentList(
++argIter; ++argIter;
++argIndex; ++argIndex;
if (exceedsLoopCount())
return;
} }
return; return;
} }
else if (state.log.getMutable<FreeTypePack>(tail)) else if (state.log.getMutable<FreeTypePack>(tail))
{ {
loopCount = 0;
// Create a type pack out of the remaining argument types // Create a type pack out of the remaining argument types
// and unify it with the tail. // and unify it with the tail.
std::vector<TypeId> rest; std::vector<TypeId> rest;
@ -4233,6 +4340,9 @@ void TypeChecker::checkArgumentList(
{ {
rest.push_back(*argIter); rest.push_back(*argIter);
++argIter; ++argIter;
if (exceedsLoopCount())
return;
} }
TypePackId varPack = addTypePack(TypePackVar{TypePack{rest, argIter.tail()}}); TypePackId varPack = addTypePack(TypePackVar{TypePack{rest, argIter.tail()}});
@ -4504,9 +4614,9 @@ std::unique_ptr<WithPredicate<TypePackId>> TypeChecker::checkCallOverload(
{ {
callTy = getIndexTypeFromType(scope, mttv->metatable, "__call", expr.func->location, /* addErrors= */ false); 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) if (callTy)
@ -5209,17 +5319,17 @@ void TypeChecker::diagnoseMissingTableKey(UnknownProperty* utk, TypeErrorData& d
if (auto ttv = getTableType(utk->table)) if (auto ttv = getTableType(utk->table))
accumulate(ttv->props); 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; break;
ctv = get<ClassType>(*ctv->parent); etv = get<ExternType>(*etv->parent);
LUAU_ASSERT(ctv); LUAU_ASSERT(etv);
} }
} }
@ -5298,8 +5408,7 @@ TypeId TypeChecker::freshType(const ScopePtr& scope)
TypeId TypeChecker::freshType(TypeLevel level) TypeId TypeChecker::freshType(TypeLevel level)
{ {
return FFlag::LuauFreeTypesMustHaveBounds ? currentModule->internalTypes.freshType(builtinTypes, level) return currentModule->internalTypes.freshType(builtinTypes, level);
: currentModule->internalTypes.addType(Type(FreeType(level)));
} }
TypeId TypeChecker::singletonType(bool value) TypeId TypeChecker::singletonType(bool value)
@ -5696,7 +5805,8 @@ TypeId TypeChecker::resolveTypeWorker(const ScopePtr& scope, const AstType& anno
auto [generics, genericPacks] = createGenericTypes(funcScope, std::nullopt, annotation, func->generics, func->genericPacks); auto [generics, genericPacks] = createGenericTypes(funcScope, std::nullopt, annotation, func->generics, func->genericPacks);
TypePackId argTypes = resolveTypePack(funcScope, func->argTypes); TypePackId argTypes = resolveTypePack(funcScope, func->argTypes);
TypePackId retTypes = resolveTypePack(funcScope, func->returnTypes); TypePackId retTypes = FFlag::LuauStoreReturnTypesAsPackOnAst ? resolveTypePack(funcScope, *func->returnTypes)
: resolveTypePack(funcScope, func->returnTypes_DEPRECATED);
std::vector<TypeId> genericTys; std::vector<TypeId> genericTys;
genericTys.reserve(generics.size()); genericTys.reserve(generics.size());
@ -5747,13 +5857,9 @@ TypeId TypeChecker::resolveTypeWorker(const ScopePtr& scope, const AstType& anno
return builtinTypes->nilType; return builtinTypes->nilType;
} }
else if (const auto& un = annotation.as<AstTypeUnion>()) else if (const auto& un = annotation.as<AstTypeUnion>())
{
if (FFlag::LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
{ {
if (un->types.size == 1) if (un->types.size == 1)
return resolveType(scope, *un->types.data[0]); return resolveType(scope, *un->types.data[0]);
}
std::vector<TypeId> types; std::vector<TypeId> types;
for (AstType* ann : un->types) for (AstType* ann : un->types)
types.push_back(resolveType(scope, *ann)); types.push_back(resolveType(scope, *ann));
@ -5761,13 +5867,9 @@ TypeId TypeChecker::resolveTypeWorker(const ScopePtr& scope, const AstType& anno
return addType(UnionType{types}); return addType(UnionType{types});
} }
else if (const auto& un = annotation.as<AstTypeIntersection>()) else if (const auto& un = annotation.as<AstTypeIntersection>())
{
if (FFlag::LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
{ {
if (un->types.size == 1) if (un->types.size == 1)
return resolveType(scope, *un->types.data[0]); return resolveType(scope, *un->types.data[0]);
}
std::vector<TypeId> types; std::vector<TypeId> types;
for (AstType* ann : un->types) for (AstType* ann : un->types)
types.push_back(resolveType(scope, *ann)); types.push_back(resolveType(scope, *ann));
@ -6374,7 +6476,7 @@ void TypeChecker::resolve(const TypeGuardPredicate& typeguardP, RefinementMap& r
return refine( return refine(
[](TypeId ty) -> bool [](TypeId ty) -> bool
{ {
return get<ClassType>(ty); return get<ExternType>(ty);
} }
); );
} }
@ -6389,13 +6491,13 @@ void TypeChecker::resolve(const TypeGuardPredicate& typeguardP, RefinementMap& r
TypeId type = follow(typeFun->type); TypeId type = follow(typeFun->type);
// You cannot refine to the top class type. // You cannot refine to the top class type.
if (type == builtinTypes->classType) if (type == builtinTypes->externType)
{ {
return addRefinement(refis, typeguardP.lvalue, errorRecoveryType(scope)); return addRefinement(refis, typeguardP.lvalue, errorRecoveryType(scope));
} }
// We're only interested in the root class of any classes. // We're only interested in the root type of any extern type.
if (auto ctv = get<ClassType>(type); !ctv || (ctv->parent != builtinTypes->classType && !hasTag(type, kTypeofRootTag))) if (auto etv = get<ExternType>(type); !etv || (etv->parent != builtinTypes->externType && !hasTag(type, kTypeofRootTag)))
return addRefinement(refis, typeguardP.lvalue, errorRecoveryType(scope)); return addRefinement(refis, typeguardP.lvalue, errorRecoveryType(scope));
// This probably hints at breaking out type filtering functions from the predicate solver so that typeof is not tightly coupled with IsA. // This probably hints at breaking out type filtering functions from the predicate solver so that typeof is not tightly coupled with IsA.

View file

@ -208,6 +208,26 @@ TypePackIterator end(TypePackId tp)
return TypePackIterator{}; 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) bool areEqual(SeenSet& seen, const TypePackVar& lhs, const TypePackVar& rhs)
{ {
TypePackId lhsId = const_cast<TypePackId>(&lhs); TypePackId lhsId = const_cast<TypePackId>(&lhs);

View file

@ -307,9 +307,9 @@ struct TraversalState
prop = &it->second; 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 // For a metatable type, the table takes priority; check that before
// falling through to the metatable entry below. // falling through to the metatable entry below.
@ -461,7 +461,7 @@ struct TraversalState
indexer = &(*mtMt->indexer); indexer = &(*mtMt->indexer);
} }
// Note: we don't appear to walk the class hierarchy for indexers // 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); indexer = &(*ct->indexer);
if (indexer) if (indexer)

View file

@ -11,10 +11,7 @@
#include <algorithm> #include <algorithm>
LUAU_FASTFLAG(LuauSolverV2); LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTFLAG(LuauAutocompleteRefactorsForIncrementalAutocomplete); LUAU_FASTFLAG(LuauNonReentrantGeneralization2)
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope);
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
LUAU_FASTFLAG(LuauNonReentrantGeneralization)
LUAU_FASTFLAG(LuauDisableNewSolverAssertsInMixedMode) LUAU_FASTFLAG(LuauDisableNewSolverAssertsInMixedMode)
namespace Luau namespace Luau
@ -308,7 +305,7 @@ TypePack extendTypePack(
TypePack newPack; TypePack newPack;
newPack.tail = arena.freshTypePack(ftp->scope, ftp->polarity); newPack.tail = arena.freshTypePack(ftp->scope, ftp->polarity);
if (FFlag::LuauNonReentrantGeneralization) if (FFlag::LuauNonReentrantGeneralization2)
trackInteriorFreeTypePack(ftp->scope, *newPack.tail); trackInteriorFreeTypePack(ftp->scope, *newPack.tail);
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
@ -327,11 +324,10 @@ TypePack extendTypePack(
{ {
FreeType ft{ftp->scope, builtinTypes->neverType, builtinTypes->unknownType, ftp->polarity}; FreeType ft{ftp->scope, builtinTypes->neverType, builtinTypes->unknownType, ftp->polarity};
t = arena.addType(ft); t = arena.addType(ft);
if (FFlag::LuauTrackInteriorFreeTypesOnScope)
trackInteriorFreeType(ftp->scope, t); trackInteriorFreeType(ftp->scope, t);
} }
else 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); 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) ErrorSuppression shouldSuppressErrors(NotNull<Normalizer> normalizer, TypeId ty)
{ {
LUAU_ASSERT(FFlag::LuauSolverV2 || FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete);
std::shared_ptr<const NormalizedType> normType = normalizer->normalize(ty); std::shared_ptr<const NormalizedType> normType = normalizer->normalize(ty);
if (!normType) if (!normType)
@ -556,10 +551,8 @@ std::vector<TypeId> findBlockedArgTypesIn(AstExprCall* expr, NotNull<DenseHashMa
void trackInteriorFreeType(Scope* scope, TypeId ty) void trackInteriorFreeType(Scope* scope, TypeId ty)
{ {
if (FFlag::LuauDisableNewSolverAssertsInMixedMode) if (!FFlag::LuauDisableNewSolverAssertsInMixedMode)
LUAU_ASSERT(FFlag::LuauTrackInteriorFreeTypesOnScope); LUAU_ASSERT(FFlag::LuauSolverV2);
else
LUAU_ASSERT(FFlag::LuauSolverV2 && FFlag::LuauTrackInteriorFreeTypesOnScope);
for (; scope; scope = scope->parent.get()) for (; scope; scope = scope->parent.get())
{ {
if (scope->interiorFreeTypes) if (scope->interiorFreeTypes)
@ -577,7 +570,7 @@ void trackInteriorFreeType(Scope* scope, TypeId ty)
void trackInteriorFreeTypePack(Scope* scope, TypePackId tp) void trackInteriorFreeTypePack(Scope* scope, TypePackId tp)
{ {
LUAU_ASSERT(tp); LUAU_ASSERT(tp);
if (!FFlag::LuauNonReentrantGeneralization) if (!FFlag::LuauNonReentrantGeneralization2)
return; return;
for (; scope; scope = scope->parent.get()) for (; scope; scope = scope->parent.get())

View file

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

View file

@ -18,9 +18,7 @@
#include <optional> #include <optional>
LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAGVARIABLE(LuauUnifyMetatableWithAny) LUAU_FASTFLAG(LuauNonReentrantGeneralization2)
LUAU_FASTFLAG(LuauExtraFollows)
LUAU_FASTFLAG(LuauNonReentrantGeneralization)
namespace Luau namespace Luau
{ {
@ -238,9 +236,9 @@ bool Unifier2::unify(TypeId subTy, TypeId superTy)
auto superMetatable = get<MetatableType>(superTy); auto superMetatable = get<MetatableType>(superTy);
if (subMetatable && superMetatable) if (subMetatable && superMetatable)
return unify(subMetatable, superMetatable); return unify(subMetatable, superMetatable);
else if (FFlag::LuauUnifyMetatableWithAny && subMetatable && superAny) else if (subMetatable && superAny)
return unify(subMetatable, superAny); return unify(subMetatable, superAny);
else if (FFlag::LuauUnifyMetatableWithAny && subAny && superMetatable) else if (subAny && superMetatable)
return unify(subAny, superMetatable); return unify(subAny, superMetatable);
else if (subMetatable) // if we only have one metatable, unify with the inner table else if (subMetatable) // if we only have one metatable, unify with the inner table
return unify(subMetatable->table, superTy); return unify(subMetatable->table, superTy);
@ -284,7 +282,7 @@ bool Unifier2::unifyFreeWithType(TypeId subTy, TypeId superTy)
if (superArgTail) if (superArgTail)
return doDefault(); return doDefault();
const IntersectionType* upperBoundIntersection = get<IntersectionType>(FFlag::LuauExtraFollows ? upperBound : subFree->upperBound); const IntersectionType* upperBoundIntersection = get<IntersectionType>(upperBound);
if (!upperBoundIntersection) if (!upperBoundIntersection)
return doDefault(); return doDefault();
@ -321,19 +319,19 @@ bool Unifier2::unify(TypeId subTy, const FunctionType* superFn)
if (shouldInstantiate) if (shouldInstantiate)
{ {
for (auto generic : subFn->generics) for (TypeId generic : subFn->generics)
{ {
const GenericType* gen = get<GenericType>(generic); const GenericType* gen = get<GenericType>(follow(generic));
LUAU_ASSERT(gen); if (gen)
genericSubstitutions[generic] = freshType(scope, gen->polarity); genericSubstitutions[generic] = freshType(scope, gen->polarity);
} }
for (auto genericPack : subFn->genericPacks) for (TypePackId genericPack : subFn->genericPacks)
{ {
if (FFlag::LuauNonReentrantGeneralization) if (FFlag::LuauNonReentrantGeneralization2)
{ {
const GenericTypePack* gen = get<GenericTypePack>(genericPack); const GenericTypePack* gen = get<GenericTypePack>(follow(genericPack));
LUAU_ASSERT(gen); if (gen)
genericPackSubstitutions[genericPack] = freshTypePack(scope, gen->polarity); genericPackSubstitutions[genericPack] = freshTypePack(scope, gen->polarity);
} }
else else
@ -651,211 +649,6 @@ bool Unifier2::unify(TypePackId subTp, TypePackId superTp)
return true; return true;
} }
struct FreeTypeSearcher : TypeVisitor
{
NotNull<Scope> scope;
explicit FreeTypeSearcher(NotNull<Scope> scope)
: TypeVisitor(/*skipBoundTypes*/ true)
, scope(scope)
{
}
Polarity polarity = Polarity::Positive;
void flip()
{
switch (polarity)
{
case Polarity::Positive:
polarity = Polarity::Negative;
break;
case Polarity::Negative:
polarity = Polarity::Positive;
break;
case Polarity::Mixed:
break;
default:
LUAU_ASSERT(!"Unreachable");
}
}
DenseHashSet<const void*> seenPositive{nullptr};
DenseHashSet<const void*> seenNegative{nullptr};
bool seenWithCurrentPolarity(const void* ty)
{
switch (polarity)
{
case Polarity::Positive:
{
if (seenPositive.contains(ty))
return true;
seenPositive.insert(ty);
return false;
}
case Polarity::Negative:
{
if (seenNegative.contains(ty))
return true;
seenNegative.insert(ty);
return false;
}
case Polarity::Mixed:
{
if (seenPositive.contains(ty) && seenNegative.contains(ty))
return true;
seenPositive.insert(ty);
seenNegative.insert(ty);
return false;
}
default:
LUAU_ASSERT(!"Unreachable");
}
return false;
}
// The keys in these maps are either TypeIds or TypePackIds. It's safe to
// mix them because we only use these pointers as unique keys. We never
// indirect them.
DenseHashMap<const void*, size_t> negativeTypes{0};
DenseHashMap<const void*, size_t> positiveTypes{0};
bool visit(TypeId ty) override
{
if (seenWithCurrentPolarity(ty))
return false;
LUAU_ASSERT(ty);
return true;
}
bool visit(TypeId ty, const FreeType& ft) override
{
if (seenWithCurrentPolarity(ty))
return false;
if (!subsumes(scope, ft.scope))
return true;
switch (polarity)
{
case Polarity::Positive:
positiveTypes[ty]++;
break;
case Polarity::Negative:
negativeTypes[ty]++;
break;
case Polarity::Mixed:
positiveTypes[ty]++;
negativeTypes[ty]++;
break;
default:
LUAU_ASSERT(!"Unreachable");
}
return true;
}
bool visit(TypeId ty, const TableType& tt) override
{
if (seenWithCurrentPolarity(ty))
return false;
if ((tt.state == TableState::Free || tt.state == TableState::Unsealed) && subsumes(scope, tt.scope))
{
switch (polarity)
{
case Polarity::Positive:
positiveTypes[ty]++;
break;
case Polarity::Negative:
negativeTypes[ty]++;
break;
case Polarity::Mixed:
positiveTypes[ty]++;
negativeTypes[ty]++;
break;
default:
LUAU_ASSERT(!"Unreachable");
}
}
for (const auto& [_name, prop] : tt.props)
{
if (prop.isReadOnly())
traverse(*prop.readTy);
else
{
LUAU_ASSERT(prop.isShared());
Polarity p = polarity;
polarity = Polarity::Mixed;
traverse(prop.type());
polarity = p;
}
}
if (tt.indexer)
{
traverse(tt.indexer->indexType);
traverse(tt.indexer->indexResultType);
}
return false;
}
bool visit(TypeId ty, const FunctionType& ft) override
{
if (seenWithCurrentPolarity(ty))
return false;
flip();
traverse(ft.argTypes);
flip();
traverse(ft.retTypes);
return false;
}
bool visit(TypeId, const ClassType&) override
{
return false;
}
bool visit(TypePackId tp, const FreeTypePack& ftp) override
{
if (seenWithCurrentPolarity(tp))
return false;
if (!subsumes(scope, ftp.scope))
return true;
switch (polarity)
{
case Polarity::Positive:
positiveTypes[tp]++;
break;
case Polarity::Negative:
negativeTypes[tp]++;
break;
case Polarity::Mixed:
positiveTypes[tp]++;
negativeTypes[tp]++;
break;
default:
LUAU_ASSERT(!"Unreachable");
}
return true;
}
};
TypeId Unifier2::mkUnion(TypeId left, TypeId right) TypeId Unifier2::mkUnion(TypeId left, TypeId right)
{ {
left = follow(left); left = follow(left);

View file

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

View file

@ -388,6 +388,7 @@ public:
std::optional<Position> separatorPosition; std::optional<Position> separatorPosition;
CstExprConstantString* stringInfo = nullptr; // only if Kind == StringProperty CstExprConstantString* stringInfo = nullptr; // only if Kind == StringProperty
Position stringPosition{0, 0}; // only if Kind == StringProperty
}; };
CstTypeTable(AstArray<Item> items, bool isArray); CstTypeTable(AstArray<Item> items, bool isArray);
@ -472,8 +473,10 @@ class CstTypePackExplicit : public CstNode
public: public:
LUAU_CST_RTTI(CstTypePackExplicit) 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 openParenthesesPosition;
Position closeParenthesesPosition; Position closeParenthesesPosition;
AstArray<Position> commaPositions; AstArray<Position> commaPositions;

View file

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

View file

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

View file

@ -18,17 +18,14 @@ LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100)
// flag so that we don't break production games by reverting syntax changes. // flag so that we don't break production games by reverting syntax changes.
// See docs/SyntaxChanges.md for an explanation. // See docs/SyntaxChanges.md for an explanation.
LUAU_FASTFLAGVARIABLE(LuauSolverV2) LUAU_FASTFLAGVARIABLE(LuauSolverV2)
LUAU_FASTFLAGVARIABLE(LuauAllowComplexTypesInGenericParams)
LUAU_FASTFLAGVARIABLE(LuauErrorRecoveryForTableTypes)
LUAU_FASTFLAGVARIABLE(LuauStoreCSTData2) LUAU_FASTFLAGVARIABLE(LuauStoreCSTData2)
LUAU_FASTFLAGVARIABLE(LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
LUAU_FASTFLAGVARIABLE(LuauAstTypeGroup3) LUAU_FASTFLAGVARIABLE(LuauAstTypeGroup3)
LUAU_FASTFLAGVARIABLE(ParserNoErrorLimit)
LUAU_FASTFLAGVARIABLE(LuauFixDoBlockEndLocation)
LUAU_FASTFLAGVARIABLE(LuauParseOptionalAsNode2) LUAU_FASTFLAGVARIABLE(LuauParseOptionalAsNode2)
LUAU_FASTFLAGVARIABLE(LuauDeclareExternType)
LUAU_FASTFLAGVARIABLE(LuauParseStringIndexer) LUAU_FASTFLAGVARIABLE(LuauParseStringIndexer)
LUAU_FASTFLAGVARIABLE(LuauTypeFunResultInAutocomplete) LUAU_FASTFLAGVARIABLE(LuauTypeFunResultInAutocomplete)
LUAU_FASTFLAGVARIABLE(LuauDeprecatedAttribute) LUAU_FASTFLAGVARIABLE(LuauDeprecatedAttribute)
LUAU_FASTFLAGVARIABLE(LuauStoreReturnTypesAsPackOnAst)
LUAU_FASTFLAGVARIABLE(LuauFixFunctionWithAttributesStartLocation) LUAU_FASTFLAGVARIABLE(LuauFixFunctionWithAttributesStartLocation)
LUAU_DYNAMIC_FASTFLAGVARIABLE(DebugLuauReportReturnTypeVariadicWithTypeSuffix, false) LUAU_DYNAMIC_FASTFLAGVARIABLE(DebugLuauReportReturnTypeVariadicWithTypeSuffix, false)
@ -557,7 +554,7 @@ AstStat* Parser::parseDo()
Location endLocation = lexer.current().location; Location endLocation = lexer.current().location;
body->hasEnd = expectMatchEndAndConsume(Lexeme::ReservedEnd, matchDo); body->hasEnd = expectMatchEndAndConsume(Lexeme::ReservedEnd, matchDo);
if (FFlag::LuauFixDoBlockEndLocation && body->hasEnd) if (body->hasEnd)
body->location.end = endLocation.end; body->location.end = endLocation.end;
if (FFlag::LuauStoreCSTData2 && options.storeCstData) if (FFlag::LuauStoreCSTData2 && options.storeCstData)
@ -790,7 +787,9 @@ AstStat* Parser::parseFunctionStat(const AstArray<AstAttr*>& attributes)
matchRecoveryStopOnToken[Lexeme::ReservedEnd]++; 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]--; matchRecoveryStopOnToken[Lexeme::ReservedEnd]--;
@ -943,7 +942,8 @@ AstStat* Parser::parseLocal(const AstArray<AstAttr*>& attributes)
matchRecoveryStopOnToken[Lexeme::ReservedEnd]++; 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]--; matchRecoveryStopOnToken[Lexeme::ReservedEnd]--;
@ -1113,7 +1113,10 @@ AstStat* Parser::parseTypeFunction(const Location& start, bool exported, Positio
size_t oldTypeFunctionDepth = typeFunctionDepth; size_t oldTypeFunctionDepth = typeFunctionDepth;
typeFunctionDepth = functionStack.size(); 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; typeFunctionDepth = oldTypeFunctionDepth;
@ -1135,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); LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
@ -1166,7 +1169,18 @@ AstDeclaredClassProp Parser::parseDeclaredClassMethod(const AstArray<AstAttr*>&
expectMatchAndConsume(')', matchParen); 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(); Location end = lexer.previousLocation();
TempVector<AstType*> vars(scratchType); TempVector<AstType*> vars(scratchType);
@ -1174,7 +1188,7 @@ AstDeclaredClassProp Parser::parseDeclaredClassMethod(const AstArray<AstAttr*>&
if (args.size() == 0 || args[0].name.name != "self" || args[0].annotation != nullptr) 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 fnName.name, fnName.location, reportTypeError(Location(start, end), {}, "'self' must be present as the unannotated first parameter"), true
}; };
} }
@ -1193,14 +1207,25 @@ AstDeclaredClassProp Parser::parseDeclaredClassMethod(const AstArray<AstAttr*>&
if (vararg && !varargAnnotation) if (vararg && !varargAnnotation)
report(start, "All declaration parameters aside from 'self' must be annotated"); report(start, "All declaration parameters aside from 'self' must be annotated");
AstType* fnType = allocator.alloc<AstTypeFunction>( AstType* fnType =
FFlag::LuauStoreReturnTypesAsPackOnAst
? allocator.alloc<AstTypeFunction>(
Location(start, end), attributes, generics, genericPacks, AstTypeList{copy(vars), varargAnnotation}, copy(varNames), retTypes 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; Location start = lexer.current().location;
@ -1229,7 +1254,18 @@ AstDeclaredClassProp Parser::parseDeclaredClassMethod_DEPRECATED()
expectMatchAndConsume(')', matchParen); 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(); Location end = lexer.previousLocation();
TempVector<AstType*> vars(scratchType); TempVector<AstType*> vars(scratchType);
@ -1237,7 +1273,7 @@ AstDeclaredClassProp Parser::parseDeclaredClassMethod_DEPRECATED()
if (args.size() == 0 || args[0].name.name != "self" || args[0].annotation != nullptr) 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 fnName.name, fnName.location, reportTypeError(Location(start, end), {}, "'self' must be present as the unannotated first parameter"), true
}; };
} }
@ -1256,11 +1292,16 @@ AstDeclaredClassProp Parser::parseDeclaredClassMethod_DEPRECATED()
if (vararg && !varargAnnotation) if (vararg && !varargAnnotation)
report(start, "All declaration parameters aside from 'self' must be annotated"); report(start, "All declaration parameters aside from 'self' must be annotated");
AstType* fnType = allocator.alloc<AstTypeFunction>( AstType* fnType =
FFlag::LuauStoreReturnTypesAsPackOnAst
? allocator.alloc<AstTypeFunction>(
Location(start, end), generics, genericPacks, AstTypeList{copy(vars), varargAnnotation}, copy(varNames), retTypes 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) AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*>& attributes)
@ -1298,7 +1339,18 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
expectMatchAndConsume(')', matchParen); 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; Location end = lexer.current().location;
TempVector<AstType*> vars(scratchType); TempVector<AstType*> vars(scratchType);
@ -1316,6 +1368,8 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
if (vararg && !varargAnnotation) if (vararg && !varargAnnotation)
return reportStatError(Location(start, end), {}, {}, "All declaration parameters must be annotated"); return reportStatError(Location(start, end), {}, {}, "All declaration parameters must be annotated");
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
return allocator.alloc<AstStatDeclareFunction>( return allocator.alloc<AstStatDeclareFunction>(
Location(start, end), Location(start, end),
attributes, attributes,
@ -1330,20 +1384,61 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
retTypes retTypes
); );
} }
else if (AstName(lexer.current().name) == "class") 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" || (FFlag::LuauDeclareExternType && AstName(lexer.current().name) == "extern"))
{
bool foundExtern = false;
if (FFlag::LuauDeclareExternType)
{
if (AstName(lexer.current().name) == "extern")
{
foundExtern = true;
nextLexeme(); 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; 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; std::optional<AstName> superName = std::nullopt;
if (AstName(lexer.current().name) == "extends") if (AstName(lexer.current().name) == "extends")
{ {
nextLexeme(); 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; AstTableIndexer* indexer = nullptr;
while (lexer.current().type != Lexeme::ReservedEnd) while (lexer.current().type != Lexeme::ReservedEnd)
@ -1370,9 +1465,9 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
if (lexer.current().type == Lexeme::ReservedFunction) if (lexer.current().type == Lexeme::ReservedFunction)
{ {
if (FFlag::LuauDeprecatedAttribute) if (FFlag::LuauDeprecatedAttribute)
props.push_back(parseDeclaredClassMethod(attributes)); props.push_back(parseDeclaredExternTypeMethod(attributes));
else else
props.push_back(parseDeclaredClassMethod_DEPRECATED()); props.push_back(parseDeclaredExternTypeMethod_DEPRECATED());
} }
else if (lexer.current().type == '[') else if (lexer.current().type == '[')
{ {
@ -1395,7 +1490,7 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
if (chars && !containsNull) if (chars && !containsNull)
{ {
props.push_back(AstDeclaredClassProp{ props.push_back(AstDeclaredExternTypeProperty{
AstName(chars->data), Location(nameBegin, nameEnd), type, false, Location(begin.location, lexer.previousLocation()) AstName(chars->data), Location(nameBegin, nameEnd), type, false, Location(begin.location, lexer.previousLocation())
}); });
} }
@ -1415,6 +1510,9 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
badIndexer = parseTableIndexer_DEPRECATED(AstTableAccess::ReadWrite, std::nullopt, begin); badIndexer = parseTableIndexer_DEPRECATED(AstTableAccess::ReadWrite, std::nullopt, begin);
// we lose all additional indexer expressions from the AST after error recovery here // we lose all additional indexer expressions from the AST after error recovery here
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"); report(badIndexer->location, "Cannot have more than one class indexer");
} }
else else
@ -1436,7 +1534,7 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
expectAndConsume(':', "property type annotation"); expectAndConsume(':', "property type annotation");
AstType* propType = parseType(); AstType* propType = parseType();
props.push_back( 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())}
); );
} }
} }
@ -1446,9 +1544,9 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
if (lexer.current().type == Lexeme::ReservedFunction) if (lexer.current().type == Lexeme::ReservedFunction)
{ {
if (FFlag::LuauDeprecatedAttribute) if (FFlag::LuauDeprecatedAttribute)
props.push_back(parseDeclaredClassMethod(attributes)); props.push_back(parseDeclaredExternTypeMethod(attributes));
else else
props.push_back(parseDeclaredClassMethod_DEPRECATED()); props.push_back(parseDeclaredExternTypeMethod_DEPRECATED());
} }
else if (lexer.current().type == '[' && else if (lexer.current().type == '[' &&
(lexer.lookahead().type == Lexeme::RawString || lexer.lookahead().type == Lexeme::QuotedString)) (lexer.lookahead().type == Lexeme::RawString || lexer.lookahead().type == Lexeme::QuotedString))
@ -1470,7 +1568,7 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
if (chars && !containsNull) if (chars && !containsNull)
{ {
props.push_back(AstDeclaredClassProp{ props.push_back(AstDeclaredExternTypeProperty{
AstName(chars->data), Location(nameBegin, nameEnd), type, false, Location(begin.location, lexer.previousLocation()) AstName(chars->data), Location(nameBegin, nameEnd), type, false, Location(begin.location, lexer.previousLocation())
}); });
} }
@ -1494,6 +1592,9 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
badIndexer = parseTableIndexer_DEPRECATED(AstTableAccess::ReadWrite, std::nullopt, lexer.current()); badIndexer = parseTableIndexer_DEPRECATED(AstTableAccess::ReadWrite, std::nullopt, lexer.current());
// we lose all additional indexer expressions from the AST after error recovery here // we lose all additional indexer expressions from the AST after error recovery here
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"); report(badIndexer->location, "Cannot have more than one class indexer");
} }
else else
@ -1517,7 +1618,7 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
expectAndConsume(':', "property type annotation"); expectAndConsume(':', "property type annotation");
AstType* propType = parseType(); AstType* propType = parseType();
props.push_back( 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())}
); );
} }
} }
@ -1526,7 +1627,7 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
Location classEnd = lexer.current().location; Location classEnd = lexer.current().location;
nextLexeme(); // skip past `end` 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")) else if (std::optional<Name> globalName = parseNameOpt("global variable name"))
{ {
@ -1535,6 +1636,10 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
AstType* type = parseType(/* in declaration context */ true); AstType* type = parseType(/* in declaration context */ true);
return allocator.alloc<AstStatDeclareGlobal>(Location(start, type->location), globalName->name, globalName->location, type); 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 else
{ {
return reportStatError(start, {}, {}, "declare must be followed by an identifier, 'function', or 'class'"); return reportStatError(start, {}, {}, "declare must be followed by an identifier, 'function', or 'class'");
@ -1640,6 +1745,8 @@ std::pair<AstExprFunction*, AstLocal*> Parser::parseFunctionBody(
const AstArray<AstAttr*>& attributes const AstArray<AstAttr*>& attributes
) )
{ {
LUAU_ASSERT(FFlag::LuauStoreReturnTypesAsPackOnAst);
Location start = matchFunction.location; Location start = matchFunction.location;
if (FFlag::LuauFixFunctionWithAttributesStartLocation) if (FFlag::LuauFixFunctionWithAttributesStartLocation)
@ -1670,7 +1777,6 @@ std::pair<AstExprFunction*, AstLocal*> Parser::parseFunctionBody(
// //
// function (t: { a: number }) end // function (t: { a: number }) end
// //
if (FFlag::LuauErrorRecoveryForTableTypes)
matchRecoveryStopOnToken[')']++; matchRecoveryStopOnToken[')']++;
TempVector<Binding> args(scratchBinding); TempVector<Binding> args(scratchBinding);
@ -1690,10 +1796,147 @@ std::pair<AstExprFunction*, AstLocal*> Parser::parseFunctionBody(
expectMatchAndConsume(')', matchParen, true); expectMatchAndConsume(')', matchParen, true);
if (FFlag::LuauErrorRecoveryForTableTypes)
matchRecoveryStopOnToken[')']--; 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; AstLocal* funLocal = nullptr;
@ -1920,8 +2163,9 @@ AstTypePack* Parser::parseTypeList(
return nullptr; 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 == ':' || lexer.current().type == Lexeme::SkinnyArrow)
{ {
if (lexer.current().type == Lexeme::SkinnyArrow) if (lexer.current().type == Lexeme::SkinnyArrow)
@ -1933,7 +2177,41 @@ std::optional<AstTypeList> Parser::parseOptionalReturnType(Position* returnSpeci
unsigned int oldRecursionCount = recursionCounter; 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 // 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. // in this type annotation, but the list wasn't wrapped in parentheses.
@ -1953,8 +2231,119 @@ std::optional<AstTypeList> Parser::parseOptionalReturnType(Position* returnSpeci
} }
// ReturnType ::= Type | `(' TypeList `)' // 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"); incrementRecursionCounter("type annotation");
Lexeme begin = lexer.current(); Lexeme begin = lexer.current();
@ -2173,6 +2562,7 @@ AstType* Parser::parseTableType(bool inDeclarationContext)
if (FFlag::LuauStoreCSTData2 && options.storeCstData) if (FFlag::LuauStoreCSTData2 && options.storeCstData)
std::tie(style, blockDepth) = extractStringDetails(); std::tie(style, blockDepth) = extractStringDetails();
Position stringPosition = lexer.current().location.begin;
AstArray<char> sourceString; AstArray<char> sourceString;
std::optional<AstArray<char>> chars = parseCharArray(options.storeCstData ? &sourceString : nullptr); std::optional<AstArray<char>> chars = parseCharArray(options.storeCstData ? &sourceString : nullptr);
@ -2197,7 +2587,8 @@ AstType* Parser::parseTableType(bool inDeclarationContext)
colonPosition, colonPosition,
tableSeparator(), tableSeparator(),
lexer.current().location.begin, lexer.current().location.begin,
allocator.alloc<CstExprConstantString>(sourceString, style, blockDepth) allocator.alloc<CstExprConstantString>(sourceString, style, blockDepth),
stringPosition
}); });
} }
else else
@ -2288,6 +2679,7 @@ AstType* Parser::parseTableType(bool inDeclarationContext)
if (FFlag::LuauStoreCSTData2 && options.storeCstData) if (FFlag::LuauStoreCSTData2 && options.storeCstData)
std::tie(style, blockDepth) = extractStringDetails(); std::tie(style, blockDepth) = extractStringDetails();
Position stringPosition = lexer.current().location.begin;
AstArray<char> sourceString; AstArray<char> sourceString;
std::optional<AstArray<char>> chars = parseCharArray(options.storeCstData ? &sourceString : nullptr); std::optional<AstArray<char>> chars = parseCharArray(options.storeCstData ? &sourceString : nullptr);
@ -2312,7 +2704,8 @@ AstType* Parser::parseTableType(bool inDeclarationContext)
colonPosition, colonPosition,
tableSeparator(), tableSeparator(),
lexer.current().location.begin, lexer.current().location.begin,
allocator.alloc<CstExprConstantString>(sourceString, style, blockDepth) allocator.alloc<CstExprConstantString>(sourceString, style, blockDepth),
stringPosition
}); });
} }
else else
@ -2408,7 +2801,7 @@ AstType* Parser::parseTableType(bool inDeclarationContext)
Location end = lexer.current().location; Location end = lexer.current().location;
if (!expectMatchAndConsume('}', matchBrace, /* searchForMissing = */ FFlag::LuauErrorRecoveryForTableTypes)) if (!expectMatchAndConsume('}', matchBrace, /* searchForMissing = */ true))
end = lexer.previousLocation(); end = lexer.previousLocation();
if (FFlag::LuauStoreCSTData2) if (FFlag::LuauStoreCSTData2)
@ -2574,13 +2967,26 @@ AstType* Parser::parseFunctionTypeTail(
expectAndConsume(Lexeme::SkinnyArrow, "function type"); 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, returnType->location), attributes, generics, genericPacks, paramTypes, paramNames, returnType
);
}
else
{
auto [endLocation, returnTypeList] = parseReturnType_DEPRECATED();
AstTypeList paramTypes = AstTypeList{params, varargAnnotation}; AstTypeList paramTypes = AstTypeList{params, varargAnnotation};
return allocator.alloc<AstTypeFunction>( return allocator.alloc<AstTypeFunction>(
Location(begin.location, endLocation), attributes, generics, genericPacks, paramTypes, paramNames, returnTypeList Location(begin.location, endLocation), attributes, generics, genericPacks, paramTypes, paramNames, returnTypeList
); );
} }
}
static bool isTypeFollow(Lexeme::Type c) 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) if (parts.size() == 1 && !isUnion && !isIntersection)
return parts[0]; return parts[0];
}
else
{
if (parts.size() == 1)
return parts[0];
}
if (isUnion && isIntersection) if (isUnion && isIntersection)
{ {
return reportTypeError( return reportTypeError(
@ -3536,7 +3933,10 @@ AstExpr* Parser::parseSimpleExpr()
Lexeme matchFunction = lexer.current(); Lexeme matchFunction = lexer.current();
nextLexeme(); nextLexeme();
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
return parseFunctionBody(false, matchFunction, AstName(), nullptr, attributes).first; 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) else if (lexer.current().type == Lexeme::Number)
{ {
@ -4087,8 +4487,6 @@ AstArray<AstTypeOrPack> Parser::parseTypeParams(Position* openingPosition, TempV
parameters.push_back({{}, typePack}); parameters.push_back({{}, typePack});
} }
else if (lexer.current().type == '(') else if (lexer.current().type == '(')
{
if (FFlag::LuauAllowComplexTypesInGenericParams)
{ {
Location begin = lexer.current().location; Location begin = lexer.current().location;
AstType* type = nullptr; AstType* type = nullptr;
@ -4152,16 +4550,6 @@ AstArray<AstTypeOrPack> Parser::parseTypeParams(Position* openingPosition, TempV
parameters.push_back({parseTypeSuffix(type, begin), {}}); parameters.push_back({parseTypeSuffix(type, begin), {}});
} }
} }
else
{
auto [type, typePack] = parseSimpleTypeOrPack();
if (typePack)
parameters.push_back({{}, typePack});
else
parameters.push_back({type, {}});
}
}
else if (lexer.current().type == '>' && parameters.empty()) else if (lexer.current().type == '>' && parameters.empty())
{ {
break; break;
@ -4678,7 +5066,7 @@ void Parser::report(const Location& location, const char* format, va_list args)
parseErrors.emplace_back(location, message); 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)); ParseError::raise(location, "Reached error limit (%d)", int(FInt::LuauParseErrorLimit));
} }

View file

@ -581,7 +581,14 @@ static bool runFile(const char* name, lua_State* GL, bool repl)
// new thread needs to have the globals sandboxed // new thread needs to have the globals sandboxed
luaL_sandboxthread(L); luaL_sandboxthread(L);
std::string chunkname = "@" + std::string(name); // ignore file extension when storing module's chunkname
std::string chunkname = "@";
std::string_view nameView = name;
if (size_t dotPos = nameView.find_last_of('.'); dotPos != std::string_view::npos)
{
nameView.remove_suffix(nameView.size() - dotPos);
}
chunkname += nameView;
std::string bytecode = Luau::compile(*source, copts()); std::string bytecode = Luau::compile(*source, copts());
int status = 0; int status = 0;

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); 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); ReplRequirer* req = static_cast<ReplRequirer*>(ctx);

View file

@ -5,8 +5,6 @@
#include "Luau/Common.h" #include "Luau/Common.h"
#include "Luau/IrData.h" #include "Luau/IrData.h"
LUAU_FASTFLAG(LuauVectorLibNativeDot);
namespace Luau namespace Luau
{ {
namespace CodeGen namespace CodeGen
@ -15,80 +13,11 @@ namespace CodeGen
struct IrBuilder; struct IrBuilder;
enum class HostMetamethod; enum class HostMetamethod;
inline bool isJumpD(LuauOpcode op) int getOpLength(LuauOpcode op);
{ bool isJumpD(LuauOpcode op);
switch (op) bool isSkipC(LuauOpcode op);
{ bool isFastCall(LuauOpcode op);
case LOP_JUMP: int getJumpTarget(uint32_t insn, uint32_t pc);
case LOP_JUMPIF:
case LOP_JUMPIFNOT:
case LOP_JUMPIFEQ:
case LOP_JUMPIFLE:
case LOP_JUMPIFLT:
case LOP_JUMPIFNOTEQ:
case LOP_JUMPIFNOTLE:
case LOP_JUMPIFNOTLT:
case LOP_FORNPREP:
case LOP_FORNLOOP:
case LOP_FORGPREP:
case LOP_FORGLOOP:
case LOP_FORGPREP_INEXT:
case LOP_FORGPREP_NEXT:
case LOP_JUMPBACK:
case LOP_JUMPXEQKNIL:
case LOP_JUMPXEQKB:
case LOP_JUMPXEQKN:
case LOP_JUMPXEQKS:
return true;
default:
return false;
}
}
inline bool isSkipC(LuauOpcode op)
{
switch (op)
{
case LOP_LOADB:
return true;
default:
return false;
}
}
inline bool isFastCall(LuauOpcode op)
{
switch (op)
{
case LOP_FASTCALL:
case LOP_FASTCALL1:
case LOP_FASTCALL2:
case LOP_FASTCALL2K:
case LOP_FASTCALL3:
return true;
default:
return false;
}
}
inline int getJumpTarget(uint32_t insn, uint32_t pc)
{
LuauOpcode op = LuauOpcode(LUAU_INSN_OP(insn));
if (isJumpD(op))
return int(pc + LUAU_INSN_D(insn) + 1);
else if (isFastCall(op))
return int(pc + LUAU_INSN_C(insn) + 2);
else if (isSkipC(op) && LUAU_INSN_C(insn))
return int(pc + LUAU_INSN_C(insn) + 1);
else if (op == LOP_JUMPX)
return int(pc + LUAU_INSN_E(insn) + 1);
else
return -1;
}
inline bool isBlockTerminator(IrCmd cmd) inline bool isBlockTerminator(IrCmd cmd)
{ {
@ -180,9 +109,6 @@ inline bool hasResult(IrCmd cmd)
case IrCmd::MUL_VEC: case IrCmd::MUL_VEC:
case IrCmd::DIV_VEC: case IrCmd::DIV_VEC:
case IrCmd::DOT_VEC: case IrCmd::DOT_VEC:
if (cmd == IrCmd::DOT_VEC)
LUAU_ASSERT(FFlag::LuauVectorLibNativeDot);
LUAU_FALLTHROUGH;
case IrCmd::UNM_VEC: case IrCmd::UNM_VEC:
case IrCmd::NOT_ANY: case IrCmd::NOT_ANY:
case IrCmd::CMP_ANY: case IrCmd::CMP_ANY:

View file

@ -7,8 +7,6 @@
#include <stdarg.h> #include <stdarg.h>
#include <stdio.h> #include <stdio.h>
LUAU_FASTFLAG(LuauVectorLibNativeDot);
namespace Luau namespace Luau
{ {
namespace CodeGen namespace CodeGen
@ -590,7 +588,6 @@ void AssemblyBuilderA64::fabs(RegisterA64 dst, RegisterA64 src)
void AssemblyBuilderA64::faddp(RegisterA64 dst, RegisterA64 src) void AssemblyBuilderA64::faddp(RegisterA64 dst, RegisterA64 src)
{ {
LUAU_ASSERT(FFlag::LuauVectorLibNativeDot);
CODEGEN_ASSERT(dst.kind == KindA64::d || dst.kind == KindA64::s); CODEGEN_ASSERT(dst.kind == KindA64::d || dst.kind == KindA64::s);
CODEGEN_ASSERT(dst.kind == src.kind); CODEGEN_ASSERT(dst.kind == src.kind);

View file

@ -6,8 +6,6 @@
#include <stdarg.h> #include <stdarg.h>
#include <stdio.h> #include <stdio.h>
LUAU_FASTFLAG(LuauVectorLibNativeDot);
namespace Luau namespace Luau
{ {
namespace CodeGen namespace CodeGen
@ -955,7 +953,6 @@ void AssemblyBuilderX64::vpinsrd(RegisterX64 dst, RegisterX64 src1, OperandX64 s
void AssemblyBuilderX64::vdpps(OperandX64 dst, OperandX64 src1, OperandX64 src2, uint8_t mask) void AssemblyBuilderX64::vdpps(OperandX64 dst, OperandX64 src1, OperandX64 src2, uint8_t mask)
{ {
LUAU_ASSERT(FFlag::LuauVectorLibNativeDot);
placeAvx("vdpps", dst, src1, src2, mask, 0x40, false, AVX_0F3A, AVX_66); placeAvx("vdpps", dst, src1, src2, mask, 0x40, false, AVX_0F3A, AVX_66);
} }

View file

@ -1,7 +1,6 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/BytecodeAnalysis.h" #include "Luau/BytecodeAnalysis.h"
#include "Luau/BytecodeUtils.h"
#include "Luau/CodeGenOptions.h" #include "Luau/CodeGenOptions.h"
#include "Luau/IrData.h" #include "Luau/IrData.h"
#include "Luau/IrUtils.h" #include "Luau/IrUtils.h"
@ -639,7 +638,7 @@ void buildBytecodeBlocks(IrFunction& function, const std::vector<uint8_t>& jumpT
bcBlocks.push_back(BytecodeBlock{nexti, -1}); bcBlocks.push_back(BytecodeBlock{nexti, -1});
} }
// Returns just terminate the block // Returns just terminate the block
else if (op == LOP_RETURN) else if (int(op) == LOP_RETURN)
{ {
bcBlocks.back().finishpc = i; bcBlocks.back().finishpc = i;
} }
@ -702,7 +701,7 @@ void analyzeBytecodeTypes(IrFunction& function, const HostIrHooks& hostHooks)
BytecodeTypes& bcType = function.bcTypes[i]; BytecodeTypes& bcType = function.bcTypes[i];
switch (op) switch (int(op))
{ {
case LOP_NOP: case LOP_NOP:
break; break;

View file

@ -1,6 +1,8 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/BytecodeSummary.h" #include "Luau/BytecodeSummary.h"
#include "Luau/BytecodeUtils.h"
#include "Luau/IrUtils.h"
#include "CodeGenLower.h" #include "CodeGenLower.h"
#include "lua.h" #include "lua.h"
@ -42,7 +44,7 @@ FunctionBytecodeSummary FunctionBytecodeSummary::fromProto(Proto* proto, unsigne
Instruction insn = proto->code[i]; Instruction insn = proto->code[i];
uint8_t op = LUAU_INSN_OP(insn); uint8_t op = LUAU_INSN_OP(insn);
summary.incCount(0, op); summary.incCount(0, op);
i += Luau::getOpLength(LuauOpcode(op)); i += getOpLength(LuauOpcode(op));
} }
return summary; return summary;

View file

@ -18,7 +18,7 @@
#endif #endif
#include <windows.h> #include <windows.h>
#elif defined(__linux__) || defined(__APPLE__) #elif (defined(__linux__) || defined(__APPLE__)) && (defined(CODEGEN_TARGET_X64) || defined(CODEGEN_TARGET_A64))
// __register_frame and __deregister_frame are defined in libgcc or libc++ // __register_frame and __deregister_frame are defined in libgcc or libc++
// (depending on how it's built). We want to declare them as weak symbols // (depending on how it's built). We want to declare them as weak symbols
@ -81,7 +81,7 @@ static int findDynamicUnwindSections(uintptr_t addr, unw_dynamic_unwind_sections
} }
#endif #endif
#if defined(__linux__) || defined(__APPLE__) #if (defined(__linux__) || defined(__APPLE__)) && (defined(CODEGEN_TARGET_X64) || defined(CODEGEN_TARGET_A64))
static void visitFdeEntries(char* pos, void (*cb)(const void*)) static void visitFdeEntries(char* pos, void (*cb)(const void*))
{ {
// When using glibc++ unwinder, we need to call __register_frame/__deregister_frame on the entire .eh_frame data // When using glibc++ unwinder, we need to call __register_frame/__deregister_frame on the entire .eh_frame data
@ -132,7 +132,7 @@ void* createBlockUnwindInfo(void* context, uint8_t* block, size_t blockSize, siz
} }
#endif #endif
#elif defined(__linux__) || defined(__APPLE__) #elif (defined(__linux__) || defined(__APPLE__)) && (defined(CODEGEN_TARGET_X64) || defined(CODEGEN_TARGET_A64))
if (!&__register_frame) if (!&__register_frame)
return nullptr; return nullptr;
@ -161,7 +161,7 @@ void destroyBlockUnwindInfo(void* context, void* unwindData)
CODEGEN_ASSERT(!"Failed to deallocate function table"); CODEGEN_ASSERT(!"Failed to deallocate function table");
#endif #endif
#elif defined(__linux__) || defined(__APPLE__) #elif (defined(__linux__) || defined(__APPLE__)) && (defined(CODEGEN_TARGET_X64) || defined(CODEGEN_TARGET_A64))
if (!&__deregister_frame) if (!&__deregister_frame)
{ {
CODEGEN_ASSERT(!"Cannot deregister unwind information"); CODEGEN_ASSERT(!"Cannot deregister unwind information");
@ -184,7 +184,7 @@ bool isUnwindSupported()
size_t verLength = sizeof(ver); size_t verLength = sizeof(ver);
// libunwind on macOS 12 and earlier (which maps to osrelease 21) assumes JIT frames use pointer authentication without a way to override that // libunwind on macOS 12 and earlier (which maps to osrelease 21) assumes JIT frames use pointer authentication without a way to override that
return sysctlbyname("kern.osrelease", ver, &verLength, NULL, 0) == 0 && atoi(ver) >= 22; return sysctlbyname("kern.osrelease", ver, &verLength, NULL, 0) == 0 && atoi(ver) >= 22;
#elif defined(__linux__) || defined(__APPLE__) #elif (defined(__linux__) || defined(__APPLE__)) && (defined(CODEGEN_TARGET_X64) || defined(CODEGEN_TARGET_A64))
return true; return true;
#else #else
return false; return false;

View file

@ -284,7 +284,6 @@ bool initHeaderFunctions(BaseCodeGenContext& codeGenContext)
codeStart codeStart
)) ))
{ {
CODEGEN_ASSERT(!"Failed to create entry function");
return false; return false;
} }

View file

@ -1,8 +1,8 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/BytecodeAnalysis.h" #include "Luau/BytecodeAnalysis.h"
#include "Luau/BytecodeUtils.h"
#include "Luau/BytecodeSummary.h" #include "Luau/BytecodeSummary.h"
#include "Luau/IrDump.h" #include "Luau/IrDump.h"
#include "Luau/IrUtils.h"
#include "CodeGenLower.h" #include "CodeGenLower.h"
@ -135,7 +135,7 @@ unsigned getInstructionCount(const Instruction* insns, const unsigned size)
for (unsigned i = 0; i < size;) for (unsigned i = 0; i < size;)
{ {
++count; ++count;
i += Luau::getOpLength(LuauOpcode(LUAU_INSN_OP(insns[i]))); i += getOpLength(LuauOpcode(LUAU_INSN_OP(insns[i])));
} }
return count; return count;
} }

View file

@ -212,7 +212,6 @@ bool initHeaderFunctions(BaseCodeGenContext& codeGenContext)
codeStart codeStart
)) ))
{ {
CODEGEN_ASSERT(!"Failed to create entry function");
return false; return false;
} }

View file

@ -3,7 +3,6 @@
#include "Luau/Bytecode.h" #include "Luau/Bytecode.h"
#include "Luau/BytecodeAnalysis.h" #include "Luau/BytecodeAnalysis.h"
#include "Luau/BytecodeUtils.h"
#include "Luau/IrData.h" #include "Luau/IrData.h"
#include "Luau/IrUtils.h" #include "Luau/IrUtils.h"
@ -177,7 +176,7 @@ void IrBuilder::buildFunctionIr(Proto* proto)
// Numeric for loops require additional processing to maintain loop stack // Numeric for loops require additional processing to maintain loop stack
// Notably, this must be performed even when the block is dead so that we maintain the pairing FORNPREP-FORNLOOP // Notably, this must be performed even when the block is dead so that we maintain the pairing FORNPREP-FORNLOOP
if (op == LOP_FORNPREP) if (int(op) == LOP_FORNPREP)
beforeInstForNPrep(*this, pc, i); beforeInstForNPrep(*this, pc, i);
// We skip dead bytecode instructions when they appear after block was already terminated // We skip dead bytecode instructions when they appear after block was already terminated
@ -199,7 +198,7 @@ void IrBuilder::buildFunctionIr(Proto* proto)
} }
// See above for FORNPREP..FORNLOOP processing // See above for FORNPREP..FORNLOOP processing
if (op == LOP_FORNLOOP) if (int(op) == LOP_FORNLOOP)
afterInstForNLoop(*this, pc); afterInstForNLoop(*this, pc);
i = nexti; i = nexti;
@ -255,7 +254,7 @@ void IrBuilder::rebuildBytecodeBasicBlocks(Proto* proto)
void IrBuilder::translateInst(LuauOpcode op, const Instruction* pc, int i) void IrBuilder::translateInst(LuauOpcode op, const Instruction* pc, int i)
{ {
switch (op) switch (int(op))
{ {
case LOP_NOP: case LOP_NOP:
break; break;
@ -478,7 +477,7 @@ void IrBuilder::translateInst(LuauOpcode op, const Instruction* pc, int i)
int ra = LUAU_INSN_A(*pc); int ra = LUAU_INSN_A(*pc);
IrOp loopRepeat = blockAtInst(i + 1 + LUAU_INSN_D(*pc)); IrOp loopRepeat = blockAtInst(i + 1 + LUAU_INSN_D(*pc));
IrOp loopExit = blockAtInst(i + getOpLength(LOP_FORGLOOP)); IrOp loopExit = blockAtInst(i + getOpLength(LuauOpcode(LOP_FORGLOOP)));
IrOp fallback = block(IrBlockKind::Fallback); IrOp fallback = block(IrBlockKind::Fallback);
inst(IrCmd::INTERRUPT, constUint(i)); inst(IrCmd::INTERRUPT, constUint(i));

View file

@ -9,8 +9,6 @@
#include <stdarg.h> #include <stdarg.h>
LUAU_FASTFLAG(LuauVectorLibNativeDot);
namespace Luau namespace Luau
{ {
namespace CodeGen namespace CodeGen
@ -182,7 +180,6 @@ const char* getCmdName(IrCmd cmd)
case IrCmd::UNM_VEC: case IrCmd::UNM_VEC:
return "UNM_VEC"; return "UNM_VEC";
case IrCmd::DOT_VEC: case IrCmd::DOT_VEC:
LUAU_ASSERT(FFlag::LuauVectorLibNativeDot);
return "DOT_VEC"; return "DOT_VEC";
case IrCmd::NOT_ANY: case IrCmd::NOT_ANY:
return "NOT_ANY"; return "NOT_ANY";

View file

@ -12,8 +12,6 @@
#include "lstate.h" #include "lstate.h"
#include "lgc.h" #include "lgc.h"
LUAU_FASTFLAG(LuauVectorLibNativeDot)
namespace Luau namespace Luau
{ {
namespace CodeGen namespace CodeGen
@ -753,8 +751,6 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next)
} }
case IrCmd::DOT_VEC: case IrCmd::DOT_VEC:
{ {
LUAU_ASSERT(FFlag::LuauVectorLibNativeDot);
inst.regA64 = regs.allocReg(KindA64::d, index); inst.regA64 = regs.allocReg(KindA64::d, index);
RegisterA64 temp = regs.allocTemp(KindA64::q); RegisterA64 temp = regs.allocTemp(KindA64::q);

View file

@ -16,8 +16,6 @@
#include "lstate.h" #include "lstate.h"
#include "lgc.h" #include "lgc.h"
LUAU_FASTFLAG(LuauVectorLibNativeDot)
namespace Luau namespace Luau
{ {
namespace CodeGen namespace CodeGen
@ -706,8 +704,6 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next)
} }
case IrCmd::DOT_VEC: case IrCmd::DOT_VEC:
{ {
LUAU_ASSERT(FFlag::LuauVectorLibNativeDot);
inst.regX64 = regs.allocRegOrReuse(SizeX64::xmmword, index, {inst.a, inst.b}); inst.regX64 = regs.allocRegOrReuse(SizeX64::xmmword, index, {inst.a, inst.b});
ScopedRegX64 tmp1{regs}; ScopedRegX64 tmp1{regs};

View file

@ -13,8 +13,6 @@
static const int kMinMaxUnrolledParams = 5; static const int kMinMaxUnrolledParams = 5;
static const int kBit32BinaryOpUnrolledParams = 5; static const int kBit32BinaryOpUnrolledParams = 5;
LUAU_FASTFLAGVARIABLE(LuauVectorLibNativeDot);
namespace Luau namespace Luau
{ {
namespace CodeGen namespace CodeGen
@ -939,26 +937,9 @@ static BuiltinImplResult translateBuiltinVectorMagnitude(
build.loadAndCheckTag(arg1, LUA_TVECTOR, build.vmExit(pcpos)); build.loadAndCheckTag(arg1, LUA_TVECTOR, build.vmExit(pcpos));
IrOp sum;
if (FFlag::LuauVectorLibNativeDot)
{
IrOp a = build.inst(IrCmd::LOAD_TVALUE, arg1, build.constInt(0)); IrOp a = build.inst(IrCmd::LOAD_TVALUE, arg1, build.constInt(0));
sum = build.inst(IrCmd::DOT_VEC, a, a); IrOp sum = build.inst(IrCmd::DOT_VEC, a, a);
}
else
{
IrOp x = build.inst(IrCmd::LOAD_FLOAT, arg1, build.constInt(0));
IrOp y = build.inst(IrCmd::LOAD_FLOAT, arg1, build.constInt(4));
IrOp z = build.inst(IrCmd::LOAD_FLOAT, arg1, build.constInt(8));
IrOp x2 = build.inst(IrCmd::MUL_NUM, x, x);
IrOp y2 = build.inst(IrCmd::MUL_NUM, y, y);
IrOp z2 = build.inst(IrCmd::MUL_NUM, z, z);
sum = build.inst(IrCmd::ADD_NUM, build.inst(IrCmd::ADD_NUM, x2, y2), z2);
}
IrOp mag = build.inst(IrCmd::SQRT_NUM, sum); IrOp mag = build.inst(IrCmd::SQRT_NUM, sum);
@ -986,8 +967,6 @@ static BuiltinImplResult translateBuiltinVectorNormalize(
build.loadAndCheckTag(arg1, LUA_TVECTOR, build.vmExit(pcpos)); build.loadAndCheckTag(arg1, LUA_TVECTOR, build.vmExit(pcpos));
if (FFlag::LuauVectorLibNativeDot)
{
IrOp a = build.inst(IrCmd::LOAD_TVALUE, arg1, build.constInt(0)); IrOp a = build.inst(IrCmd::LOAD_TVALUE, arg1, build.constInt(0));
IrOp sum = build.inst(IrCmd::DOT_VEC, a, a); IrOp sum = build.inst(IrCmd::DOT_VEC, a, a);
@ -1000,29 +979,6 @@ static BuiltinImplResult translateBuiltinVectorNormalize(
result = build.inst(IrCmd::TAG_VECTOR, result); result = build.inst(IrCmd::TAG_VECTOR, result);
build.inst(IrCmd::STORE_TVALUE, build.vmReg(ra), result); build.inst(IrCmd::STORE_TVALUE, build.vmReg(ra), result);
}
else
{
IrOp x = build.inst(IrCmd::LOAD_FLOAT, arg1, build.constInt(0));
IrOp y = build.inst(IrCmd::LOAD_FLOAT, arg1, build.constInt(4));
IrOp z = build.inst(IrCmd::LOAD_FLOAT, arg1, build.constInt(8));
IrOp x2 = build.inst(IrCmd::MUL_NUM, x, x);
IrOp y2 = build.inst(IrCmd::MUL_NUM, y, y);
IrOp z2 = build.inst(IrCmd::MUL_NUM, z, z);
IrOp sum = build.inst(IrCmd::ADD_NUM, build.inst(IrCmd::ADD_NUM, x2, y2), z2);
IrOp mag = build.inst(IrCmd::SQRT_NUM, sum);
IrOp inv = build.inst(IrCmd::DIV_NUM, build.constDouble(1.0), mag);
IrOp xr = build.inst(IrCmd::MUL_NUM, x, inv);
IrOp yr = build.inst(IrCmd::MUL_NUM, y, inv);
IrOp zr = build.inst(IrCmd::MUL_NUM, z, inv);
build.inst(IrCmd::STORE_VECTOR, build.vmReg(ra), xr, yr, zr);
build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TVECTOR));
}
return {BuiltinImplType::Full, 1}; return {BuiltinImplType::Full, 1};
} }
@ -1074,31 +1030,10 @@ static BuiltinImplResult translateBuiltinVectorDot(IrBuilder& build, int nparams
build.loadAndCheckTag(arg1, LUA_TVECTOR, build.vmExit(pcpos)); build.loadAndCheckTag(arg1, LUA_TVECTOR, build.vmExit(pcpos));
build.loadAndCheckTag(args, LUA_TVECTOR, build.vmExit(pcpos)); build.loadAndCheckTag(args, LUA_TVECTOR, build.vmExit(pcpos));
IrOp sum;
if (FFlag::LuauVectorLibNativeDot)
{
IrOp a = build.inst(IrCmd::LOAD_TVALUE, arg1, build.constInt(0)); IrOp a = build.inst(IrCmd::LOAD_TVALUE, arg1, build.constInt(0));
IrOp b = build.inst(IrCmd::LOAD_TVALUE, args, build.constInt(0)); IrOp b = build.inst(IrCmd::LOAD_TVALUE, args, build.constInt(0));
sum = build.inst(IrCmd::DOT_VEC, a, b); IrOp sum = build.inst(IrCmd::DOT_VEC, a, b);
}
else
{
IrOp x1 = build.inst(IrCmd::LOAD_FLOAT, arg1, build.constInt(0));
IrOp x2 = build.inst(IrCmd::LOAD_FLOAT, args, build.constInt(0));
IrOp xx = build.inst(IrCmd::MUL_NUM, x1, x2);
IrOp y1 = build.inst(IrCmd::LOAD_FLOAT, arg1, build.constInt(4));
IrOp y2 = build.inst(IrCmd::LOAD_FLOAT, args, build.constInt(4));
IrOp yy = build.inst(IrCmd::MUL_NUM, y1, y2);
IrOp z1 = build.inst(IrCmd::LOAD_FLOAT, arg1, build.constInt(8));
IrOp z2 = build.inst(IrCmd::LOAD_FLOAT, args, build.constInt(8));
IrOp zz = build.inst(IrCmd::MUL_NUM, z1, z2);
sum = build.inst(IrCmd::ADD_NUM, build.inst(IrCmd::ADD_NUM, xx, yy), zz);
}
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(ra), sum); build.inst(IrCmd::STORE_DOUBLE, build.vmReg(ra), sum);
build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TNUMBER)); build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TNUMBER));

View file

@ -2,7 +2,6 @@
#include "IrTranslation.h" #include "IrTranslation.h"
#include "Luau/Bytecode.h" #include "Luau/Bytecode.h"
#include "Luau/BytecodeUtils.h"
#include "Luau/CodeGenOptions.h" #include "Luau/CodeGenOptions.h"
#include "Luau/IrBuilder.h" #include "Luau/IrBuilder.h"
#include "Luau/IrUtils.h" #include "Luau/IrUtils.h"
@ -1502,7 +1501,7 @@ bool translateInstNamecall(IrBuilder& build, const Instruction* pc, int pcpos)
return false; return false;
} }
IrOp next = build.blockAtInst(pcpos + getOpLength(LOP_NAMECALL)); IrOp next = build.blockAtInst(pcpos + getOpLength(LuauOpcode(LOP_NAMECALL)));
IrOp fallback = build.block(IrBlockKind::Fallback); IrOp fallback = build.block(IrBlockKind::Fallback);
IrOp firstFastPathSuccess = build.block(IrBlockKind::Internal); IrOp firstFastPathSuccess = build.block(IrBlockKind::Internal);
IrOp secondFastPath = build.block(IrBlockKind::Internal); IrOp secondFastPath = build.block(IrBlockKind::Internal);

View file

@ -16,13 +16,120 @@
#include <limits.h> #include <limits.h>
#include <math.h> #include <math.h>
LUAU_FASTFLAG(LuauVectorLibNativeDot);
namespace Luau namespace Luau
{ {
namespace CodeGen namespace CodeGen
{ {
int getOpLength(LuauOpcode op)
{
switch (int(op))
{
case LOP_GETGLOBAL:
case LOP_SETGLOBAL:
case LOP_GETIMPORT:
case LOP_GETTABLEKS:
case LOP_SETTABLEKS:
case LOP_NAMECALL:
case LOP_JUMPIFEQ:
case LOP_JUMPIFLE:
case LOP_JUMPIFLT:
case LOP_JUMPIFNOTEQ:
case LOP_JUMPIFNOTLE:
case LOP_JUMPIFNOTLT:
case LOP_NEWTABLE:
case LOP_SETLIST:
case LOP_FORGLOOP:
case LOP_LOADKX:
case LOP_FASTCALL2:
case LOP_FASTCALL2K:
case LOP_FASTCALL3:
case LOP_JUMPXEQKNIL:
case LOP_JUMPXEQKB:
case LOP_JUMPXEQKN:
case LOP_JUMPXEQKS:
return 2;
default:
return 1;
}
}
bool isJumpD(LuauOpcode op)
{
switch (int(op))
{
case LOP_JUMP:
case LOP_JUMPIF:
case LOP_JUMPIFNOT:
case LOP_JUMPIFEQ:
case LOP_JUMPIFLE:
case LOP_JUMPIFLT:
case LOP_JUMPIFNOTEQ:
case LOP_JUMPIFNOTLE:
case LOP_JUMPIFNOTLT:
case LOP_FORNPREP:
case LOP_FORNLOOP:
case LOP_FORGPREP:
case LOP_FORGLOOP:
case LOP_FORGPREP_INEXT:
case LOP_FORGPREP_NEXT:
case LOP_JUMPBACK:
case LOP_JUMPXEQKNIL:
case LOP_JUMPXEQKB:
case LOP_JUMPXEQKN:
case LOP_JUMPXEQKS:
return true;
default:
return false;
}
}
bool isSkipC(LuauOpcode op)
{
switch (int(op))
{
case LOP_LOADB:
return true;
default:
return false;
}
}
bool isFastCall(LuauOpcode op)
{
switch (int(op))
{
case LOP_FASTCALL:
case LOP_FASTCALL1:
case LOP_FASTCALL2:
case LOP_FASTCALL2K:
case LOP_FASTCALL3:
return true;
default:
return false;
}
}
int getJumpTarget(uint32_t insn, uint32_t pc)
{
LuauOpcode op = LuauOpcode(LUAU_INSN_OP(insn));
if (isJumpD(op))
return int(pc + LUAU_INSN_D(insn) + 1);
else if (isFastCall(op))
return int(pc + LUAU_INSN_C(insn) + 2);
else if (isSkipC(op) && LUAU_INSN_C(insn))
return int(pc + LUAU_INSN_C(insn) + 1);
else if (int(op) == LOP_JUMPX)
return int(pc + LUAU_INSN_E(insn) + 1);
else
return -1;
}
IrValueKind getCmdValueKind(IrCmd cmd) IrValueKind getCmdValueKind(IrCmd cmd)
{ {
switch (cmd) switch (cmd)
@ -83,7 +190,6 @@ IrValueKind getCmdValueKind(IrCmd cmd)
case IrCmd::UNM_VEC: case IrCmd::UNM_VEC:
return IrValueKind::Tvalue; return IrValueKind::Tvalue;
case IrCmd::DOT_VEC: case IrCmd::DOT_VEC:
LUAU_ASSERT(FFlag::LuauVectorLibNativeDot);
return IrValueKind::Double; return IrValueKind::Double;
case IrCmd::NOT_ANY: case IrCmd::NOT_ANY:
case IrCmd::CMP_ANY: case IrCmd::CMP_ANY:

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