Compare commits

..

13 commits

Author SHA1 Message Date
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
menarulalam
a8d14596e7
Sync to upstream/release/669 (#1770)
We have lots of new changes for you! 

# What's Changed
## General

- We updated Luau's license year to 2025! 
- We fixed a bug where large amounts of errors were being printed when
deep intersections of unions error.


## Require-by-String
This release introduces the `Luau.Require` library, which exposes the
runtime semantics of require-by-string, including support for the new
`@self` alias described in [this
RFC](https://github.com/luau-lang/rfcs/pull/109).

The library operates on a virtualized filesystem, allowing consumers to
specify navigation rules without assuming a filesystem context.
Documentation in `Require.h` explains how to enable the library, and the
`setupState` function in Repl.cpp demonstrates how we've integrated it
into the luau CLI tool. Note that the interface in `Require.h` is
written in C, which enables any application written in a language with a
C foreign-function interface to link against this library and enable
require-by-string. This makes it straightforward for any application
embedding Luau to support require-by-string, provided that it defines or
operates within an environment resembling a virtual filesystem.

The core navigation semantics of require-by-string have additionally
been pulled into the `Luau.RequireNavigator` library. While
`Luau.Require` internally depends on `Luau.RequireNavigator`, the latter
does not depend on the Luau VM. This library provides an interface for
inspecting require-by-string's navigation behavior and therefore serves
as a useful dependency for static tooling. Documentation for
`Luau.RequireNavigator` is available in `RequireNavigator.h`.
## Autocomplete
- We fixed a memory leak in fragment autocomplete!
## New Solver And Old Solver
- We've found a infinite iteration error over a type pack. We added a
way to detect this error and throw an `InternalCompileError` instead.
- We fix `table.freeze` not accounting for the first argument not
getting type stated. We fall back to regular inference instead.
- We fix a crash in the old solver with `length_error`.
- We fix a crash in the new solver stemming from generalization
reentrancy. Now we correctly generalize interior free types that do not
appear in a function signature.
- We fix a nil refinement. (Fixes
https://github.com/luau-lang/luau/issues/1687 and
https://github.com/luau-lang/luau/issues/1451)



### Internal Contributors
Co-authored-by: Andy Friesen <afriesen@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: Vyacheslav Egorov <vegorov@roblox.com>

Full Changelog: https://github.com/luau-lang/luau/compare/0.668...0.669

---------

Co-authored-by: Hunter Goldstein <hgoldstein@roblox.com>
Co-authored-by: Varun Saini <61795485+vrn-sn@users.noreply.github.com>
Co-authored-by: Alexander Youngblood <ayoungblood@roblox.com>
Co-authored-by: Aviral Goel <agoel@roblox.com>
Co-authored-by: Vighnesh <vvijay@roblox.com>
Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
Co-authored-by: Ariel Weiss <aaronweiss@roblox.com>
2025-04-11 17:44:21 -07:00
vegorov-rbx
cbe078b3b4
Enable the A64 unwinding test (#1769) 2025-04-11 11:33:06 -07:00
Aviral Goel
ee1c6bf0db
Sync to upstream/release/668 (#1760)
## New Type Solver

1. Update resolved types for singleton unions and intersections to avoid
crashing when type checking type assertions.
2. Generalize free return type pack of a function type inferred at call
site to ensure that the free type does not leak to another module.
3. Fix crash from cyclic indexers by reducing if possible or producing
an error otherwise.
4. Fix handling of irreducible type functions to prevent type inference
from failing.
5. Fix handling of recursive metatables to avoid infinite recursion.

## New and Old Type Solver

Fix accidental capture of all exceptions in multi-threaded typechecking
by converting all typechecking exceptions to `InternalCompilerError` and
only capturing those.

## Fragment Autocomplete

1. Add a block based diff algorithm based on class index and span for
re-typechecking. This reduces the granularity of fragment autocomplete
to avoid flakiness when the fragment does not have enough type
information.
2. Fix bugs arising from incorrect scope selection for autocompletion.

## Roundtrippable AST

Store type alias location in `TypeFun` class to ensure it is accessible
for exported types as part of the public interface.

## Build System

1. Bump minimum supported CMake version to 3.10 since GitHub is phasing
out the currently supported minimum version 3.0, released 11 years ago.
2. Fix compilation when `HARDSTACKTESTS` is enabled.

## Miscellaneous

Flag removals and cleanup of unused code.

## 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: Vighnesh Vijay <vvijay@roblox.com>
Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>

## External Contributors

Thanks to [@grh-official](https://github.com/grh-official) for PR #1759 

**Full Changelog**:
https://github.com/luau-lang/luau/compare/0.667...0.668

---------

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: Vighnesh <vvijay@roblox.com>
Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
Co-authored-by: Ariel Weiss <aaronweiss@roblox.com>
2025-04-04 14:11:51 -07:00
ayoungbloodrbx
6b33251b89
Sync to upstream/release/667 (#1754)
After a very auspicious release last week, we have a new bevy of changes
for you!

## What's Changed

### Deprecated Attribute

This release includes an implementation of the `@deprecated` attribute
proposed in [this
RFC](https://rfcs.luau.org/syntax-attribute-functions-deprecated.html).
It relies on the new type solver to propagate deprecation information
from function and method AST nodes to the corresponding type objects.
These objects are queried by a linter pass when it encounters local,
global, or indexed variables, to issue deprecation warnings. Uses of
deprecated functions and methods in recursion are ignored. To support
deprecation of class methods, the parser has been extended to allow
attribute declarations on class methods. The implementation does not
support parameters, so it is not currently possible for users to
customize deprecation messages.

### General

- Add a limit for normalization of function types.

### New Type Solver

- Fix type checker to accept numbers as concat operands (Fixes #1671).
- Fix user-defined type functions failing when used inside type
aliases/nested calls (Fixes #1738, Fixes #1679).
- Improve constraint generation for overloaded functions (in part thanks
to @vvatheus in #1694).
- Improve type inference for indexers on table literals, especially when
passing table literals directly as a function call argument.
- Equate regular error type and intersection with a negation of an error
type.
- Avoid swapping types in 2-part union when RHS is optional.
- Use simplification when doing `~nil` refinements.
- `len<>` now works on metatables without `__len` function.

### AST

- Retain source information for `AstTypeUnion` and
`AstTypeIntersection`.

### Transpiler

- Print attributes on functions.

### Parser

- Allow types in indexers to begin with string literals by @jackdotink
in #1750.

### Autocomplete

- Evaluate user-defined type functions in ill-formed source code to
provide autocomplete.
- Fix the start location of functions that have attributes.
- Implement better fragment selection.

### Internal Contributors

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>

**Full Changelog**:
https://github.com/luau-lang/luau/compare/0.666...0.667

---------

Co-authored-by: Hunter Goldstein <hgoldstein@roblox.com>
Co-authored-by: Varun Saini <61795485+vrn-sn@users.noreply.github.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: Ariel Weiss <aaronweiss@roblox.com>
2025-03-28 16:15:46 -07:00
Jack
12dac2f1f4
fix parsing string union indexers (#1750)
Today this code results in a syntax error: `type foo = { ["bar" |
"baz"]: number }`. This is odd and I believe it is a bug. I have fixed
this so that it is now parsed as an indexer field with a union type.
This change should not affect the way any code is parsed today, and
allow types in indexers to begin with string literals.

---------

Co-authored-by: ariel <aweiss@hey.com>
2025-03-25 16:18:22 -07:00
Matheus
2621488abe
Fix singleton parameters in overloaded functions (#1694)
- Fixes #1691 
- Fixes #1589

---------

Co-authored-by: Math <175355178+maffeus@users.noreply.github.com>
Co-authored-by: ariel <aaronweiss@roblox.com>
Co-authored-by: Matheus <175355178+m4fh@users.noreply.github.com>
Co-authored-by: ariel <aweiss@hey.com>
2025-03-24 09:27:13 -07:00
Varun Saini
5f42e63a73
Sync to upstream/release/666 (#1747)
Another week, another release. Happy spring! 🌷 

## New Type Solver

- Add typechecking and autocomplete support for user-defined type
functions!
- Improve the display of type paths, making type mismatch errors far
more human-readable.
- Enhance various aspects of the `index` type function: support function
type metamethods, fix crashes involving cyclic metatables, and forward
`any` types through the type function.
- Fix incorrect subtyping results involving the `buffer` type.
- Fix crashes related to typechecking anonymous functions in nonstrict
mode.

## AST

- Retain source information for type packs, functions, and type
functions.
- Introduce `AstTypeOptional` to differentiate `T?` from `T | nil` in
the AST.
- Prevent the transpiler from advancing before tokens when the AST has
errors.

## Autocomplete

- Introduce demand-based cloning and better module isolation for
fragment autocomplete, leading to a substantial speedup in performance.
- Guard against recursive unions in `autocompleteProps`.

## Miscellaneous

- #1720 (thank you!)

## 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: Vighnesh Vijay <vvijay@roblox.com>
Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
2025-03-21 14:43:00 -07:00
Hunter Goldstein
e0b55a9cb1
Sync to upstream/release/665 (#1732)
Hello all! Another week, another Luau release!

# Change to `lua_setuserdatametatable`

This release fixes #1710: `lua_setuserdatametatable` is being changed so
that it _only_ operates on the top of the stack: the `idx` parameter is
being removed. Prior to this, `lua_setuserdatametable` would set the
metatable of the value in the stack at `idx`, but _always_ pop the top
of the stack. The old behavior is available in this release as
`lua_setuserdatametatable_DEPRECATED`.

# General

This release exposes a generalized implementation of require-by-string's
autocomplete logic. `FileResolver` can now be optionally constructed
with a `RequireSuggester`, which provides an interface for converting a
given module to a `RequireNode`. Consumers of this new API implement a
`RequireNode` to define how modules are represented in their embedded
context, and the new API manages the logic specific to
require-by-string, including providing suggestions for require aliases.
This enhancement moves toward integrating require-by-string's semantics
into the language itself, rather than merely providing a specification
for community members to implement themselves.

# New Type Solver
* Fixed a source of potential `Luau::follow detected a Type cycle`
internal compiler exceptions when assigning a global to itself.
* Fixed an issue whereby `*no-refine*` (a type which should not be
visible at the end of type checking) was not being properly elided,
causing inference of class-like tables to become unreadable / induce
crashes in autocomplete.
* Fixed a case of incomplete constraint solving when performing basic
math in a loop

# Fragment Autocomplete
* Fixed several crashes related to not properly filling in scope
information for the fragments
* Fixed a source of memory corruption by isolating the return type of a
fragment when it is type checked.
* Improved performance by opting not to clone persistent types for the
fragment (e.g.: built in types)
 
# Internal Contributors
Co-authored-by: Andy Friesen <afriesen@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: Vyacheslav Egorov <vegorov@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: Ariel Weiss <aaronweiss@roblox.com>
2025-03-14 13:11:24 -07:00
Kostadin
b0c3f40b0c
Add #include <stdint.h> to fix building with gcc 15 (#1720)
With gcc 15, the C++ Standard Library no longer includes other headers
that were internally used by the library. In Luau's case the missing
header is `<stdint.h>`

Downstream Gentoo bug: https://bugs.gentoo.org/938122
Signed-off-by: Kostadin Shishmanov <kostadinshishmanov@protonmail.com>

---------

Co-authored-by: vegorov-rbx <75688451+vegorov-rbx@users.noreply.github.com>
2025-03-10 06:02:09 -07:00
vegorov-rbx
de9f5d6eb6
Sync to upstream/release/664 (#1715)
As always, a weekly Luau update!
This week we have further improvements to new type solver, fixing a few
of the popular issues reported. The fragment autocomplete is even more
stable and we believe it's ready for broader use.

Aside from that we have a few general fixes/improvements:
* Fixed data race when multi-threaded typechecking is used, appearing as
a random crash at the end of typechecking
* AST data is now available from `Luau::Module`

## New Type Solver

* Fixed type refinements made by function calls which could attach `nil`
as an option of a type before (Fixes #1528)
* Improved bidirectional typechecking in tables (Fixes #1596)
* Fixed normalization of negated types
* `getmetatable()` on `any` type should no longer report an error

## Fragment Autocomplete

* Fixed auto-complete suggestions being provided inside multiline
comments
* Fixed an assertion failure that could happen when old type solver was
used
* Fixed issues with missing suggestions when multiple statements are on
the same line
* Fixed memory safety issues

## Internal Contributors

Co-authored-by: Andy Friesen <afriesen@roblox.com>
Co-authored-by: Hunter Goldstein <hgoldstein@roblox.com>
Co-authored-by: Vighnesh Vijay <vvijay@roblox.com>
Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
2025-03-07 10:07:27 -08:00
180 changed files with 14659 additions and 5770 deletions

View file

@ -1,148 +0,0 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "Luau/AstQuery.h"
#include "Luau/Config.h"
#include "Luau/ModuleResolver.h"
#include "Luau/Scope.h"
#include "Luau/Variant.h"
#include "Luau/Normalize.h"
#include "Luau/TypePack.h"
#include "Luau/TypeArena.h"
#include <mutex>
#include <string>
#include <vector>
#include <optional>
namespace Luau
{
class AstStat;
class ParseError;
struct TypeError;
struct LintWarning;
struct GlobalTypes;
struct ModuleResolver;
struct ParseResult;
struct DcrLogger;
struct TelemetryTypePair
{
std::string annotatedType;
std::string inferredType;
};
struct AnyTypeSummary
{
TypeArena arena;
AstStatBlock* rootSrc = nullptr;
DenseHashSet<TypeId> seenTypeFamilyInstances{nullptr};
int recursionCount = 0;
std::string root;
int strictCount = 0;
DenseHashMap<const void*, bool> seen{nullptr};
AnyTypeSummary();
void traverse(const Module* module, AstStat* src, NotNull<BuiltinTypes> builtinTypes);
std::pair<bool, TypeId> checkForAnyCast(const Scope* scope, AstExprTypeAssertion* expr);
bool containsAny(TypePackId typ);
bool containsAny(TypeId typ);
bool isAnyCast(const Scope* scope, AstExpr* expr, const Module* module, NotNull<BuiltinTypes> builtinTypes);
bool isAnyCall(const Scope* scope, AstExpr* expr, const Module* module, NotNull<BuiltinTypes> builtinTypes);
bool hasVariadicAnys(const Scope* scope, AstExprFunction* expr, const Module* module, NotNull<BuiltinTypes> builtinTypes);
bool hasArgAnys(const Scope* scope, AstExprFunction* expr, const Module* module, NotNull<BuiltinTypes> builtinTypes);
bool hasAnyReturns(const Scope* scope, AstExprFunction* expr, const Module* module, NotNull<BuiltinTypes> builtinTypes);
TypeId checkForFamilyInhabitance(const TypeId instance, Location location);
TypeId lookupType(const AstExpr* expr, const Module* module, NotNull<BuiltinTypes> builtinTypes);
TypePackId reconstructTypePack(const AstArray<AstExpr*> exprs, const Module* module, NotNull<BuiltinTypes> builtinTypes);
DenseHashSet<TypeId> seenTypeFunctionInstances{nullptr};
TypeId lookupAnnotation(AstType* annotation, const Module* module, NotNull<BuiltinTypes> builtintypes);
std::optional<TypePackId> lookupPackAnnotation(AstTypePack* annotation, const Module* module);
TypeId checkForTypeFunctionInhabitance(const TypeId instance, const Location location);
enum Pattern : uint64_t
{
Casts,
FuncArg,
FuncRet,
FuncApp,
VarAnnot,
VarAny,
TableProp,
Alias,
Assign,
TypePk
};
struct TypeInfo
{
Pattern code;
std::string node;
TelemetryTypePair type;
explicit TypeInfo(Pattern code, std::string node, TelemetryTypePair type);
};
struct FindReturnAncestry final : public AstVisitor
{
AstNode* currNode{nullptr};
AstNode* stat{nullptr};
Position rootEnd;
bool found = false;
explicit FindReturnAncestry(AstNode* stat, Position rootEnd);
bool visit(AstType* node) override;
bool visit(AstNode* node) override;
bool visit(AstStatFunction* node) override;
bool visit(AstStatLocalFunction* node) override;
};
std::vector<TypeInfo> typeInfo;
/**
* Fabricates a scope that is a child of another scope.
* @param node the lexical node that the scope belongs to.
* @param parent the parent scope of the new scope. Must not be null.
*/
const Scope* childScope(const AstNode* node, const Scope* parent);
std::optional<AstExpr*> matchRequire(const AstExprCall& call);
AstNode* getNode(AstStatBlock* root, AstNode* node);
const Scope* findInnerMostScope(const Location location, const Module* module);
const AstNode* findAstAncestryAtLocation(const AstStatBlock* root, AstNode* node);
void visit(const Scope* scope, AstStat* stat, const Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(const Scope* scope, AstStatBlock* block, const Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(const Scope* scope, AstStatIf* ifStatement, const Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(const Scope* scope, AstStatWhile* while_, const Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(const Scope* scope, AstStatRepeat* repeat, const Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(const Scope* scope, AstStatReturn* ret, const Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(const Scope* scope, AstStatLocal* local, const Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(const Scope* scope, AstStatFor* for_, const Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(const Scope* scope, AstStatForIn* forIn, const Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(const Scope* scope, AstStatAssign* assign, const Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(const Scope* scope, AstStatCompoundAssign* assign, const Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(const Scope* scope, AstStatFunction* function, const Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(const Scope* scope, AstStatLocalFunction* function, const Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(const Scope* scope, AstStatTypeAlias* alias, const Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(const Scope* scope, AstStatExpr* expr, const Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(const Scope* scope, AstStatDeclareGlobal* declareGlobal, const Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(const Scope* scope, AstStatDeclareClass* declareClass, const Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(const Scope* scope, AstStatDeclareFunction* declareFunction, const Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(const Scope* scope, AstStatError* error, const Module* module, NotNull<BuiltinTypes> builtinTypes);
};
} // namespace Luau

View file

@ -70,6 +70,7 @@ Property makeProperty(TypeId ty, std::optional<std::string> documentationSymbol
void assignPropDocumentationSymbols(TableType::Props& props, const std::string& baseName); void assignPropDocumentationSymbols(TableType::Props& props, const std::string& baseName);
std::string getBuiltinDefinitionSource(); std::string getBuiltinDefinitionSource();
std::string getTypeFunctionDefinitionSource();
void addGlobalBinding(GlobalTypes& globals, const std::string& name, TypeId ty, const std::string& packageName); void addGlobalBinding(GlobalTypes& globals, const std::string& name, TypeId ty, const std::string& packageName);
void addGlobalBinding(GlobalTypes& globals, const std::string& name, Binding binding); void addGlobalBinding(GlobalTypes& globals, const std::string& name, Binding binding);

View file

@ -40,4 +40,9 @@ TypeId clone(TypeId tp, TypeArena& dest, CloneState& cloneState);
TypeFun clone(const TypeFun& typeFun, TypeArena& dest, CloneState& cloneState); TypeFun clone(const TypeFun& typeFun, TypeArena& dest, CloneState& cloneState);
Binding clone(const Binding& binding, TypeArena& dest, CloneState& cloneState); Binding clone(const Binding& binding, TypeArena& dest, CloneState& cloneState);
TypePackId cloneIncremental(TypePackId tp, TypeArena& dest, CloneState& cloneState, Scope* freshScopeForFreeTypes);
TypeId cloneIncremental(TypeId typeId, TypeArena& dest, CloneState& cloneState, Scope* freshScopeForFreeTypes);
TypeFun cloneIncremental(const TypeFun& typeFun, TypeArena& dest, CloneState& cloneState, Scope* freshScopeForFreeTypes);
Binding cloneIncremental(const Binding& binding, TypeArena& dest, CloneState& cloneState, Scope* freshScopeForFreeTypes);
} // namespace Luau } // namespace Luau

View file

@ -50,6 +50,7 @@ struct GeneralizationConstraint
TypeId sourceType; TypeId sourceType;
std::vector<TypeId> interiorTypes; std::vector<TypeId> interiorTypes;
bool hasDeprecatedAttribute = false;
}; };
// variables ~ iterate iterator // variables ~ iterate iterator

View file

@ -11,15 +11,14 @@
#include "Luau/ModuleResolver.h" #include "Luau/ModuleResolver.h"
#include "Luau/Normalize.h" #include "Luau/Normalize.h"
#include "Luau/NotNull.h" #include "Luau/NotNull.h"
#include "Luau/Polarity.h"
#include "Luau/Refinement.h" #include "Luau/Refinement.h"
#include "Luau/Symbol.h" #include "Luau/Symbol.h"
#include "Luau/TypeFwd.h" #include "Luau/TypeFwd.h"
#include "Luau/TypeUtils.h" #include "Luau/TypeUtils.h"
#include "Luau/Variant.h"
#include <memory> #include <memory>
#include <vector> #include <vector>
#include <unordered_map>
namespace Luau namespace Luau
{ {
@ -117,18 +116,23 @@ struct ConstraintGenerator
// Needed to register all available type functions for execution at later stages. // Needed to register all available type functions for execution at later stages.
NotNull<TypeFunctionRuntime> typeFunctionRuntime; NotNull<TypeFunctionRuntime> typeFunctionRuntime;
DenseHashMap<const AstStatTypeFunction*, ScopePtr> astTypeFunctionEnvironmentScopes{nullptr};
// Needed to resolve modules to make 'require' import types properly. // Needed to resolve modules to make 'require' import types properly.
NotNull<ModuleResolver> moduleResolver; NotNull<ModuleResolver> moduleResolver;
// Occasionally constraint generation needs to produce an ICE. // Occasionally constraint generation needs to produce an ICE.
const NotNull<InternalErrorReporter> ice; const NotNull<InternalErrorReporter> ice;
ScopePtr globalScope; ScopePtr globalScope;
ScopePtr typeFunctionScope;
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope; std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope;
std::vector<RequireCycle> requireCycles; std::vector<RequireCycle> requireCycles;
DenseHashMap<TypeId, TypeIds> localTypes{nullptr}; DenseHashMap<TypeId, TypeIds> localTypes{nullptr};
DenseHashMap<AstExpr*, Inference> inferredExprCache{nullptr};
DcrLogger* logger; DcrLogger* logger;
ConstraintGenerator( ConstraintGenerator(
@ -140,6 +144,7 @@ struct ConstraintGenerator
NotNull<BuiltinTypes> builtinTypes, NotNull<BuiltinTypes> builtinTypes,
NotNull<InternalErrorReporter> ice, NotNull<InternalErrorReporter> ice,
const ScopePtr& globalScope, const ScopePtr& globalScope,
const ScopePtr& typeFunctionScope,
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope, std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope,
DcrLogger* logger, DcrLogger* logger,
NotNull<DataFlowGraph> dfg, NotNull<DataFlowGraph> dfg,
@ -156,19 +161,26 @@ struct ConstraintGenerator
void visitFragmentRoot(const ScopePtr& resumeScope, AstStatBlock* block); void visitFragmentRoot(const ScopePtr& resumeScope, AstStatBlock* block);
private: private:
std::vector<std::vector<TypeId>> interiorTypes; struct InteriorFreeTypes
{
std::vector<TypeId> types;
std::vector<TypePackId> typePacks;
};
std::vector<std::vector<TypeId>> DEPRECATED_interiorTypes;
std::vector<InteriorFreeTypes> interiorFreeTypes;
/** /**
* Fabricates a new free type belonging to a given scope. * Fabricates a new free type belonging to a given scope.
* @param scope the scope the free type belongs to. * @param scope the scope the free type belongs to.
*/ */
TypeId freshType(const ScopePtr& scope); TypeId freshType(const ScopePtr& scope, Polarity polarity = Polarity::Unknown);
/** /**
* Fabricates a new free type pack belonging to a given scope. * Fabricates a new free type pack belonging to a given scope.
* @param scope the scope the free type pack belongs to. * @param scope the scope the free type pack belongs to.
*/ */
TypePackId freshTypePack(const ScopePtr& scope); TypePackId freshTypePack(const ScopePtr& scope, Polarity polarity = Polarity::Unknown);
/** /**
* Allocate a new TypePack with the given head and tail. * Allocate a new TypePack with the given head and tail.
@ -289,7 +301,7 @@ private:
); );
Inference check(const ScopePtr& scope, AstExprConstantString* string, std::optional<TypeId> expectedType, bool forceSingleton); Inference check(const ScopePtr& scope, AstExprConstantString* string, std::optional<TypeId> expectedType, bool forceSingleton);
Inference check(const ScopePtr& scope, AstExprConstantBool* bool_, std::optional<TypeId> expectedType, bool forceSingleton); Inference check(const ScopePtr& scope, AstExprConstantBool* boolExpr, std::optional<TypeId> expectedType, bool forceSingleton);
Inference check(const ScopePtr& scope, AstExprLocal* local); Inference check(const ScopePtr& scope, AstExprLocal* local);
Inference check(const ScopePtr& scope, AstExprGlobal* global); Inference check(const ScopePtr& scope, AstExprGlobal* global);
Inference checkIndexName(const ScopePtr& scope, const RefinementKey* key, AstExpr* indexee, const std::string& index, Location indexLocation); Inference checkIndexName(const ScopePtr& scope, const RefinementKey* key, AstExpr* indexee, const std::string& index, Location indexLocation);
@ -365,6 +377,11 @@ private:
**/ **/
TypeId resolveType(const ScopePtr& scope, AstType* ty, bool inTypeArguments, bool replaceErrorWithFresh = false); TypeId resolveType(const ScopePtr& scope, AstType* ty, bool inTypeArguments, bool replaceErrorWithFresh = false);
// resolveType() is recursive, but we only want to invoke
// inferGenericPolarities() once at the very end. We thus isolate the
// recursive part of the algorithm to this internal helper.
TypeId resolveType_(const ScopePtr& scope, AstType* ty, bool inTypeArguments, bool replaceErrorWithFresh = false);
/** /**
* Resolves a type pack from its AST annotation. * Resolves a type pack from its AST annotation.
* @param scope the scope that the type annotation appears within. * @param scope the scope that the type annotation appears within.
@ -374,6 +391,9 @@ private:
**/ **/
TypePackId resolveTypePack(const ScopePtr& scope, AstTypePack* tp, bool inTypeArguments, bool replaceErrorWithFresh = false); TypePackId resolveTypePack(const ScopePtr& scope, AstTypePack* tp, bool inTypeArguments, bool replaceErrorWithFresh = false);
// Inner hepler for resolveTypePack
TypePackId resolveTypePack_(const ScopePtr& scope, AstTypePack* tp, bool inTypeArguments, bool replaceErrorWithFresh = false);
/** /**
* Resolves a type pack from its AST annotation. * Resolves a type pack from its AST annotation.
* @param scope the scope that the type annotation appears within. * @param scope the scope that the type annotation appears within.
@ -412,7 +432,7 @@ private:
**/ **/
std::vector<std::pair<Name, GenericTypePackDefinition>> createGenericPacks( std::vector<std::pair<Name, GenericTypePackDefinition>> createGenericPacks(
const ScopePtr& scope, const ScopePtr& scope,
AstArray<AstGenericTypePack*> packs, AstArray<AstGenericTypePack*> generics,
bool useCache = false, bool useCache = false,
bool addTypes = true bool addTypes = true
); );

View file

@ -365,7 +365,7 @@ public:
* @returns a non-free type that generalizes the argument, or `std::nullopt` if one * @returns a non-free type that generalizes the argument, or `std::nullopt` if one
* does not exist * does not exist
*/ */
std::optional<TypeId> generalizeFreeType(NotNull<Scope> scope, TypeId type, bool avoidSealingTables = false); std::optional<TypeId> generalizeFreeType(NotNull<Scope> scope, TypeId type);
/** /**
* Checks the existing set of constraints to see if there exist any that contain * Checks the existing set of constraints to see if there exist any that contain

View file

@ -38,8 +38,6 @@ struct DataFlowGraph
DefId getDef(const AstExpr* expr) const; DefId getDef(const AstExpr* expr) const;
// Look up the definition optionally, knowing it may not be present. // Look up the definition optionally, knowing it may not be present.
std::optional<DefId> getDefOptional(const AstExpr* expr) const; std::optional<DefId> getDefOptional(const AstExpr* expr) const;
// Look up for the rvalue def for a compound assignment.
std::optional<DefId> getRValueDefForCompoundAssign(const AstExpr* expr) const;
DefId getDef(const AstLocal* local) const; DefId getDef(const AstLocal* local) const;
@ -66,10 +64,6 @@ private:
// All keys in this maps are really only statements that ambiently declares a symbol. // All keys in this maps are really only statements that ambiently declares a symbol.
DenseHashMap<const AstStat*, const Def*> declaredDefs{nullptr}; DenseHashMap<const AstStat*, const Def*> declaredDefs{nullptr};
// Compound assignments are in a weird situation where the local being assigned to is also being used at its
// previous type implicitly in an rvalue position. This map provides the previous binding.
DenseHashMap<const AstExpr*, const Def*> compoundAssignDefs{nullptr};
DenseHashMap<const AstExpr*, const RefinementKey*> astRefinementKeys{nullptr}; DenseHashMap<const AstExpr*, const RefinementKey*> astRefinementKeys{nullptr};
friend struct DataFlowGraphBuilder; friend struct DataFlowGraphBuilder;
}; };
@ -135,8 +129,8 @@ private:
/// A stack of scopes used by the visitor to see where we are. /// A stack of scopes used by the visitor to see where we are.
ScopeStack scopeStack; ScopeStack scopeStack;
NotNull<DfgScope> currentScope();
DfgScope* currentScope(); DfgScope* currentScope_DEPRECATED();
struct FunctionCapture struct FunctionCapture
{ {
@ -154,8 +148,8 @@ private:
void joinBindings(DfgScope* p, const DfgScope& a, const DfgScope& b); void joinBindings(DfgScope* p, const DfgScope& a, const DfgScope& b);
void joinProps(DfgScope* p, const DfgScope& a, const DfgScope& b); void joinProps(DfgScope* p, const DfgScope& a, const DfgScope& b);
DefId lookup(Symbol symbol); DefId lookup(Symbol symbol, Location location);
DefId lookup(DefId def, const std::string& key); DefId lookup(DefId def, const std::string& key, Location location);
ControlFlow visit(AstStatBlock* b); ControlFlow visit(AstStatBlock* b);
ControlFlow visitBlockWithoutChildScope(AstStatBlock* b); ControlFlow visitBlockWithoutChildScope(AstStatBlock* b);

View file

@ -4,7 +4,8 @@
#include "Luau/NotNull.h" #include "Luau/NotNull.h"
#include "Luau/TypedAllocator.h" #include "Luau/TypedAllocator.h"
#include "Luau/Variant.h" #include "Luau/Variant.h"
#include "Luau/Location.h"
#include "Luau/Symbol.h"
#include <string> #include <string>
#include <optional> #include <optional>
@ -13,6 +14,7 @@ namespace Luau
struct Def; struct Def;
using DefId = NotNull<const Def>; using DefId = NotNull<const Def>;
struct AstLocal;
/** /**
* A cell is a "single-object" value. * A cell is a "single-object" value.
@ -64,6 +66,8 @@ struct Def
using V = Variant<struct Cell, struct Phi>; using V = Variant<struct Cell, struct Phi>;
V v; V v;
Symbol name;
Location location;
}; };
template<typename T> template<typename T>
@ -79,7 +83,7 @@ struct DefArena
{ {
TypedAllocator<Def> allocator; TypedAllocator<Def> allocator;
DefId freshCell(bool subscripted = false); DefId freshCell(Symbol sym, Location location, bool subscripted = false);
DefId phi(DefId a, DefId b); DefId phi(DefId a, DefId b);
DefId phi(const std::vector<DefId>& defs); DefId phi(const std::vector<DefId>& defs);
}; };

View file

@ -1,8 +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
#pragma once #pragma once
#include <string> #include <memory>
#include <optional> #include <optional>
#include <string>
#include <vector> #include <vector>
namespace Luau namespace Luau
@ -32,15 +33,71 @@ struct ModuleInfo
bool optional = false; bool optional = false;
}; };
struct RequireAlias
{
std::string alias; // Unprefixed alias name (no leading `@`).
std::vector<std::string> tags = {};
};
struct RequireNode
{
virtual ~RequireNode() {}
// Get the path component representing this node.
virtual std::string getPathComponent() const = 0;
// Get the displayed user-facing label for this node, defaults to getPathComponent()
virtual std::string getLabel() const
{
return getPathComponent();
}
// Get tags to attach to this node's RequireSuggestion (defaults to none).
virtual std::vector<std::string> getTags() const
{
return {};
}
// TODO: resolvePathToNode() can ultimately be replaced with a call into
// require-by-string's path resolution algorithm. This will first require
// generalizing that algorithm to work with a virtual file system.
virtual std::unique_ptr<RequireNode> resolvePathToNode(const std::string& path) const = 0;
// Get children of this node, if any (if this node represents a directory).
virtual std::vector<std::unique_ptr<RequireNode>> getChildren() const = 0;
// A list of the aliases available to this node.
virtual std::vector<RequireAlias> getAvailableAliases() const = 0;
};
struct RequireSuggestion struct RequireSuggestion
{ {
std::string label; std::string label;
std::string fullPath; std::string fullPath;
std::vector<std::string> tags;
}; };
using RequireSuggestions = std::vector<RequireSuggestion>; using RequireSuggestions = std::vector<RequireSuggestion>;
struct RequireSuggester
{
virtual ~RequireSuggester() {}
std::optional<RequireSuggestions> getRequireSuggestions(const ModuleName& requirer, const std::optional<std::string>& pathString) const;
protected:
virtual std::unique_ptr<RequireNode> getNode(const ModuleName& name) const = 0;
private:
std::optional<RequireSuggestions> getRequireSuggestionsImpl(const ModuleName& requirer, const std::optional<std::string>& path) const;
};
struct FileResolver struct FileResolver
{ {
FileResolver() = default;
FileResolver(std::shared_ptr<RequireSuggester> requireSuggester)
: requireSuggester(std::move(requireSuggester))
{
}
virtual ~FileResolver() {} virtual ~FileResolver() {}
virtual std::optional<SourceCode> readSource(const ModuleName& name) = 0; virtual std::optional<SourceCode> readSource(const ModuleName& name) = 0;
@ -60,10 +117,10 @@ struct FileResolver
return std::nullopt; return std::nullopt;
} }
virtual std::optional<RequireSuggestions> getRequireSuggestions(const ModuleName& requirer, const std::optional<std::string>& pathString) const // Make non-virtual when removing FFlagLuauImproveRequireByStringAutocomplete.
{ virtual std::optional<RequireSuggestions> getRequireSuggestions(const ModuleName& requirer, const std::optional<std::string>& pathString) const;
return std::nullopt;
} std::shared_ptr<RequireSuggester> requireSuggester;
}; };
struct NullFileResolver : FileResolver struct NullFileResolver : FileResolver

View file

@ -15,6 +15,28 @@ namespace Luau
{ {
struct FrontendOptions; struct FrontendOptions;
enum class FragmentAutocompleteWaypoint
{
ParseFragmentEnd,
CloneModuleStart,
CloneModuleEnd,
DfgBuildEnd,
CloneAndSquashScopeStart,
CloneAndSquashScopeEnd,
ConstraintSolverStart,
ConstraintSolverEnd,
TypecheckFragmentEnd,
AutocompleteEnd,
COUNT,
};
class IFragmentAutocompleteReporter
{
public:
virtual void reportWaypoint(FragmentAutocompleteWaypoint) = 0;
virtual void reportFragmentString(std::string_view) = 0;
};
enum class FragmentTypeCheckStatus enum class FragmentTypeCheckStatus
{ {
SkipAutocomplete, SkipAutocomplete,
@ -27,6 +49,8 @@ struct FragmentAutocompleteAncestryResult
std::vector<AstLocal*> localStack; std::vector<AstLocal*> localStack;
std::vector<AstNode*> ancestry; std::vector<AstNode*> ancestry;
AstStat* nearestStatement = nullptr; AstStat* nearestStatement = nullptr;
AstStatBlock* parentBlock = nullptr;
Location fragmentSelectionRegion;
}; };
struct FragmentParseResult struct FragmentParseResult
@ -37,6 +61,7 @@ struct FragmentParseResult
AstStat* nearestStatement = nullptr; AstStat* nearestStatement = nullptr;
std::vector<Comment> commentLocations; std::vector<Comment> commentLocations;
std::unique_ptr<Allocator> alloc = std::make_unique<Allocator>(); std::unique_ptr<Allocator> alloc = std::make_unique<Allocator>();
Position scopePos{0, 0};
}; };
struct FragmentTypeCheckResult struct FragmentTypeCheckResult
@ -50,14 +75,34 @@ struct FragmentAutocompleteResult
{ {
ModulePtr incrementalModule; ModulePtr incrementalModule;
Scope* freshScope; Scope* freshScope;
TypeArena arenaForAutocomplete; TypeArena arenaForAutocomplete_DEPRECATED;
AutocompleteResult acResults; AutocompleteResult acResults;
}; };
FragmentAutocompleteAncestryResult findAncestryForFragmentParse(AstStatBlock* root, const Position& cursorPos); struct FragmentRegion
{
Location fragmentLocation;
AstStat* nearestStatement = nullptr; // used for tests
AstStatBlock* parentBlock = nullptr; // used for scope detection
};
std::optional<Position> blockDiffStart(AstStatBlock* blockOld, AstStatBlock* blockNew, AstStat* nearestStatementNewAst);
FragmentRegion getFragmentRegion(AstStatBlock* root, const Position& cursorPosition);
FragmentAutocompleteAncestryResult findAncestryForFragmentParse(AstStatBlock* stale, const Position& cursorPos, AstStatBlock* lastGoodParse);
FragmentAutocompleteAncestryResult findAncestryForFragmentParse_DEPRECATED(AstStatBlock* root, const Position& cursorPos);
std::optional<FragmentParseResult> parseFragment_DEPRECATED(
AstStatBlock* root,
AstNameTable* names,
std::string_view src,
const Position& cursorPos,
std::optional<Position> fragmentEndPosition
);
std::optional<FragmentParseResult> parseFragment( std::optional<FragmentParseResult> parseFragment(
const SourceModule& srcModule, AstStatBlock* stale,
AstStatBlock* mostRecentParse,
AstNameTable* names,
std::string_view src, std::string_view src,
const Position& cursorPos, const Position& cursorPos,
std::optional<Position> fragmentEndPosition std::optional<Position> fragmentEndPosition
@ -69,7 +114,9 @@ std::pair<FragmentTypeCheckStatus, FragmentTypeCheckResult> typecheckFragment(
const Position& cursorPos, const Position& cursorPos,
std::optional<FrontendOptions> opts, std::optional<FrontendOptions> opts,
std::string_view src, std::string_view src,
std::optional<Position> fragmentEndPosition std::optional<Position> fragmentEndPosition,
AstStatBlock* recentParse = nullptr,
IFragmentAutocompleteReporter* reporter = nullptr
); );
FragmentAutocompleteResult fragmentAutocomplete( FragmentAutocompleteResult fragmentAutocomplete(
@ -79,7 +126,9 @@ FragmentAutocompleteResult fragmentAutocomplete(
Position cursorPosition, Position cursorPosition,
std::optional<FrontendOptions> opts, std::optional<FrontendOptions> opts,
StringCompletionCallback callback, StringCompletionCallback callback,
std::optional<Position> fragmentEndPosition = std::nullopt std::optional<Position> fragmentEndPosition = std::nullopt,
AstStatBlock* recentParse = nullptr,
IFragmentAutocompleteReporter* reporter = nullptr
); );
enum class FragmentAutocompleteStatus enum class FragmentAutocompleteStatus
@ -98,9 +147,10 @@ struct FragmentAutocompleteStatusResult
struct FragmentContext struct FragmentContext
{ {
std::string_view newSrc; std::string_view newSrc;
const ParseResult& newAstRoot; const ParseResult& freshParse;
std::optional<FrontendOptions> opts; std::optional<FrontendOptions> opts;
std::optional<Position> DEPRECATED_fragmentEndPosition; std::optional<Position> DEPRECATED_fragmentEndPosition;
IFragmentAutocompleteReporter* reporter = nullptr;
}; };
/** /**

View file

@ -10,7 +10,6 @@
#include "Luau/Set.h" #include "Luau/Set.h"
#include "Luau/TypeCheckLimits.h" #include "Luau/TypeCheckLimits.h"
#include "Luau/Variant.h" #include "Luau/Variant.h"
#include "Luau/AnyTypeSummary.h"
#include <mutex> #include <mutex>
#include <string> #include <string>
@ -32,8 +31,8 @@ struct ModuleResolver;
struct ParseResult; struct ParseResult;
struct HotComment; struct HotComment;
struct BuildQueueItem; struct BuildQueueItem;
struct BuildQueueWorkState;
struct FrontendCancellationToken; struct FrontendCancellationToken;
struct AnyTypeSummary;
struct LoadDefinitionFileResult struct LoadDefinitionFileResult
{ {
@ -251,6 +250,9 @@ private:
void checkBuildQueueItem(BuildQueueItem& item); void checkBuildQueueItem(BuildQueueItem& item);
void checkBuildQueueItems(std::vector<BuildQueueItem>& items); void checkBuildQueueItems(std::vector<BuildQueueItem>& items);
void recordItemResult(const BuildQueueItem& item); void recordItemResult(const BuildQueueItem& item);
void performQueueItemTask(std::shared_ptr<BuildQueueWorkState> state, size_t itemPos);
void sendQueueItemTask(std::shared_ptr<BuildQueueWorkState> state, size_t itemPos);
void sendQueueCycleItemTask(std::shared_ptr<BuildQueueWorkState> state);
static LintResult classifyLints(const std::vector<LintWarning>& warnings, const Config& config); static LintResult classifyLints(const std::vector<LintWarning>& warnings, const Config& config);
@ -296,6 +298,7 @@ ModulePtr check(
NotNull<ModuleResolver> moduleResolver, NotNull<ModuleResolver> moduleResolver,
NotNull<FileResolver> fileResolver, NotNull<FileResolver> fileResolver,
const ScopePtr& globalScope, const ScopePtr& globalScope,
const ScopePtr& typeFunctionScope,
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope, std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope,
FrontendOptions options, FrontendOptions options,
TypeCheckLimits limits TypeCheckLimits limits
@ -310,6 +313,7 @@ ModulePtr check(
NotNull<ModuleResolver> moduleResolver, NotNull<ModuleResolver> moduleResolver,
NotNull<FileResolver> fileResolver, NotNull<FileResolver> fileResolver,
const ScopePtr& globalScope, const ScopePtr& globalScope,
const ScopePtr& typeFunctionScope,
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope, std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope,
FrontendOptions options, FrontendOptions options,
TypeCheckLimits limits, TypeCheckLimits limits,

View file

@ -8,12 +8,39 @@
namespace Luau namespace Luau
{ {
template<typename TID>
struct GeneralizationParams
{
bool foundOutsideFunctions = false;
size_t useCount = 0;
Polarity polarity = Polarity::None;
};
// Replace a single free type by its bounds according to the polarity provided.
std::optional<TypeId> generalizeType(
NotNull<TypeArena> arena,
NotNull<BuiltinTypes> builtinTypes,
NotNull<Scope> scope,
TypeId freeTy,
const GeneralizationParams<TypeId>& params
);
// Generalize one type pack
std::optional<TypePackId> generalizeTypePack(
NotNull<TypeArena> arena,
NotNull<BuiltinTypes> builtinTypes,
NotNull<Scope> scope,
TypePackId tp,
const GeneralizationParams<TypePackId>& params
);
void sealTable(NotNull<Scope> scope, TypeId ty);
std::optional<TypeId> generalize( std::optional<TypeId> generalize(
NotNull<TypeArena> arena, NotNull<TypeArena> arena,
NotNull<BuiltinTypes> builtinTypes, NotNull<BuiltinTypes> builtinTypes,
NotNull<Scope> scope, NotNull<Scope> scope,
NotNull<DenseHashSet<TypeId>> bakedTypes, NotNull<DenseHashSet<TypeId>> cachedTypes,
TypeId ty, TypeId ty
/* avoid sealing tables*/ bool avoidSealingTables = false
); );
} }

View file

@ -19,7 +19,9 @@ struct GlobalTypes
TypeArena globalTypes; TypeArena globalTypes;
SourceModule globalNames; // names for symbols entered into globalScope SourceModule globalNames; // names for symbols entered into globalScope
ScopePtr globalScope; // shared by all modules ScopePtr globalScope; // shared by all modules
ScopePtr globalTypeFunctionScope; // shared by all modules
}; };
} // namespace Luau } // namespace Luau

View file

@ -0,0 +1,16 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "Luau/NotNull.h"
#include "Luau/TypeFwd.h"
namespace Luau
{
struct Scope;
struct TypeArena;
void inferGenericPolarities(NotNull<TypeArena> arena, NotNull<Scope> scope, TypeId ty);
void inferGenericPolarities(NotNull<TypeArena> arena, NotNull<Scope> scope, TypePackId tp);
} // namespace Luau

View file

@ -67,6 +67,19 @@ public:
return &pairs.at(it->second).second; return &pairs.at(it->second).second;
} }
V& operator[](const K& k)
{
auto it = indices.find(k);
if (it == indices.end())
{
pairs.push_back(std::make_pair(k, V()));
indices[k] = pairs.size() - 1;
return pairs.back().second;
}
else
return pairs.at(it->second).second;
}
const_iterator begin() const const_iterator begin() const
{ {
return pairs.begin(); return pairs.begin();

View file

@ -8,7 +8,6 @@
#include "Luau/ParseResult.h" #include "Luau/ParseResult.h"
#include "Luau/Scope.h" #include "Luau/Scope.h"
#include "Luau/TypeArena.h" #include "Luau/TypeArena.h"
#include "Luau/AnyTypeSummary.h"
#include "Luau/DataFlowGraph.h" #include "Luau/DataFlowGraph.h"
#include <memory> #include <memory>
@ -16,19 +15,16 @@
#include <unordered_map> #include <unordered_map>
#include <optional> #include <optional>
LUAU_FASTFLAG(LuauIncrementalAutocompleteCommentDetection)
namespace Luau namespace Luau
{ {
using LogLuauProc = void (*)(std::string_view); using LogLuauProc = void (*)(std::string_view, std::string_view);
extern LogLuauProc logLuau; extern LogLuauProc logLuau;
void setLogLuau(LogLuauProc ll); void setLogLuau(LogLuauProc ll);
void resetLogLuauProc(); void resetLogLuauProc();
struct Module; struct Module;
struct AnyTypeSummary;
using ScopePtr = std::shared_ptr<struct Scope>; using ScopePtr = std::shared_ptr<struct Scope>;
using ModulePtr = std::shared_ptr<Module>; using ModulePtr = std::shared_ptr<Module>;
@ -86,13 +82,10 @@ struct Module
TypeArena interfaceTypes; TypeArena interfaceTypes;
TypeArena internalTypes; TypeArena internalTypes;
// Summary of Ast Nodes that either contain
// user annotated anys or typechecker inferred anys
AnyTypeSummary ats{};
// Scopes and AST types refer to parse data, so we need to keep that alive // Scopes and AST types refer to parse data, so we need to keep that alive
std::shared_ptr<Allocator> allocator; std::shared_ptr<Allocator> allocator;
std::shared_ptr<AstNameTable> names; std::shared_ptr<AstNameTable> names;
AstStatBlock* root = nullptr;
std::vector<std::pair<Location, ScopePtr>> scopes; // never empty std::vector<std::pair<Location, ScopePtr>> scopes; // never empty

View file

@ -1,9 +1,10 @@
// 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
#pragma once #pragma once
#include "Luau/DataFlowGraph.h"
#include "Luau/EqSatSimplification.h"
#include "Luau/Module.h" #include "Luau/Module.h"
#include "Luau/NotNull.h" #include "Luau/NotNull.h"
#include "Luau/DataFlowGraph.h"
namespace Luau namespace Luau
{ {

View file

@ -0,0 +1,68 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include <cstdint>
namespace Luau
{
enum struct Polarity : uint8_t
{
None = 0b000,
Positive = 0b001,
Negative = 0b010,
Mixed = 0b011,
Unknown = 0b100,
};
inline Polarity operator|(Polarity lhs, Polarity rhs)
{
return Polarity(uint8_t(lhs) | uint8_t(rhs));
}
inline Polarity& operator|=(Polarity& lhs, Polarity rhs)
{
lhs = lhs | rhs;
return lhs;
}
inline Polarity operator&(Polarity lhs, Polarity rhs)
{
return Polarity(uint8_t(lhs) & uint8_t(rhs));
}
inline Polarity& operator&=(Polarity& lhs, Polarity rhs)
{
lhs = lhs & rhs;
return lhs;
}
inline bool isPositive(Polarity p)
{
return bool(p & Polarity::Positive);
}
inline bool isNegative(Polarity p)
{
return bool(p & Polarity::Negative);
}
inline bool isKnown(Polarity p)
{
return p != Polarity::Unknown;
}
inline Polarity invert(Polarity p)
{
switch (p)
{
case Polarity::Positive:
return Polarity::Negative;
case Polarity::Negative:
return Polarity::Positive;
default:
return p;
}
}
} // namespace Luau

View file

@ -31,13 +31,4 @@ struct OrderedMap
} }
}; };
struct QuantifierResult
{
TypeId result;
OrderedMap<TypeId, TypeId> insertedGenerics;
OrderedMap<TypePackId, TypePackId> insertedGenericPacks;
};
std::optional<QuantifierResult> quantify(TypeArena* arena, TypeId ty, Scope* scope);
} // namespace Luau } // namespace Luau

View file

@ -53,6 +53,7 @@ struct Proposition
{ {
const RefinementKey* key; const RefinementKey* key;
TypeId discriminantTy; TypeId discriminantTy;
bool implicitFromCall;
}; };
template<typename T> template<typename T>
@ -69,6 +70,7 @@ struct RefinementArena
RefinementId disjunction(RefinementId lhs, RefinementId rhs); RefinementId disjunction(RefinementId lhs, RefinementId rhs);
RefinementId equivalence(RefinementId lhs, RefinementId rhs); RefinementId equivalence(RefinementId lhs, RefinementId rhs);
RefinementId proposition(const RefinementKey* key, TypeId discriminantTy); RefinementId proposition(const RefinementKey* key, TypeId discriminantTy);
RefinementId implicitProposition(const RefinementKey* key, TypeId discriminantTy);
private: private:
TypedAllocator<Refinement> allocator; TypedAllocator<Refinement> allocator;

View file

@ -35,12 +35,12 @@ struct Scope
explicit Scope(TypePackId returnType); // root scope explicit Scope(TypePackId returnType); // root scope
explicit Scope(const ScopePtr& parent, int subLevel = 0); // child scope. Parent must not be nullptr. explicit Scope(const ScopePtr& parent, int subLevel = 0); // child scope. Parent must not be nullptr.
const ScopePtr parent; // null for the root ScopePtr parent; // null for the root
// All the children of this scope. // All the children of this scope.
std::vector<NotNull<Scope>> children; std::vector<NotNull<Scope>> children;
std::unordered_map<Symbol, Binding> bindings; std::unordered_map<Symbol, Binding> bindings;
TypePackId returnType; TypePackId returnType = nullptr;
std::optional<TypePackId> varargPack; std::optional<TypePackId> varargPack;
TypeLevel level; TypeLevel level;
@ -59,6 +59,8 @@ struct Scope
std::optional<TypeId> lookup(Symbol sym) const; std::optional<TypeId> lookup(Symbol sym) const;
std::optional<TypeId> lookupUnrefinedType(DefId def) const; std::optional<TypeId> lookupUnrefinedType(DefId def) const;
std::optional<TypeId> lookupRValueRefinementType(DefId def) const;
std::optional<TypeId> lookup(DefId def) const; std::optional<TypeId> lookup(DefId def) const;
std::optional<std::pair<TypeId, Scope*>> lookupEx(DefId def); std::optional<std::pair<TypeId, Scope*>> lookupEx(DefId def);
std::optional<std::pair<Binding*, Scope*>> lookupEx(Symbol sym); std::optional<std::pair<Binding*, Scope*>> lookupEx(Symbol sym);
@ -71,6 +73,7 @@ struct Scope
// WARNING: This function linearly scans for a string key of equal value! It is thus O(n**2) // WARNING: This function linearly scans for a string key of equal value! It is thus O(n**2)
std::optional<Binding> linearSearchForBinding(const std::string& name, bool traverseScopeChain = true) const; std::optional<Binding> linearSearchForBinding(const std::string& name, bool traverseScopeChain = true) const;
std::optional<std::pair<Symbol, Binding>> linearSearchForBindingPair(const std::string& name, bool traverseScopeChain) const;
RefinementMap refinements; RefinementMap refinements;
@ -97,6 +100,7 @@ struct Scope
std::unordered_map<Name, TypePackId> typeAliasTypePackParameters; std::unordered_map<Name, TypePackId> typeAliasTypePackParameters;
std::optional<std::vector<TypeId>> interiorFreeTypes; std::optional<std::vector<TypeId>> interiorFreeTypes;
std::optional<std::vector<TypePackId>> interiorFreeTypePacks;
}; };
// Returns true iff the left scope encloses the right scope. A Scope* equal to // Returns true iff the left scope encloses the right scope. A Scope* equal to

View file

@ -14,6 +14,7 @@ namespace Luau
struct TypeArena; struct TypeArena;
struct BuiltinTypes; struct BuiltinTypes;
struct Unifier2; struct Unifier2;
struct Subtyping;
class AstExpr; class AstExpr;
TypeId matchLiteralType( TypeId matchLiteralType(
@ -22,6 +23,7 @@ TypeId matchLiteralType(
NotNull<BuiltinTypes> builtinTypes, NotNull<BuiltinTypes> builtinTypes,
NotNull<TypeArena> arena, NotNull<TypeArena> arena,
NotNull<Unifier2> unifier, NotNull<Unifier2> unifier,
NotNull<Subtyping> subtyping,
TypeId expectedType, TypeId expectedType,
TypeId exprType, TypeId exprType,
const AstExpr* expr, const AstExpr* expr,

View file

@ -192,16 +192,6 @@ struct TxnLog
// The pointer returned lives until `commit` or `clear` is called. // The pointer returned lives until `commit` or `clear` is called.
PendingTypePack* changeLevel(TypePackId tp, TypeLevel newLevel); PendingTypePack* changeLevel(TypePackId tp, TypeLevel newLevel);
// Queues the replacement of a type's scope with the provided scope.
//
// The pointer returned lives until `commit` or `clear` is called.
PendingType* changeScope(TypeId ty, NotNull<Scope> scope);
// Queues the replacement of a type pack's scope with the provided scope.
//
// The pointer returned lives until `commit` or `clear` is called.
PendingTypePack* changeScope(TypePackId tp, NotNull<Scope> scope);
// Queues a replacement of a table type with another table type with a new // Queues a replacement of a table type with another table type with a new
// indexer. // indexer.
// //

View file

@ -5,10 +5,11 @@
#include "Luau/Ast.h" #include "Luau/Ast.h"
#include "Luau/Common.h" #include "Luau/Common.h"
#include "Luau/Refinement.h"
#include "Luau/DenseHash.h" #include "Luau/DenseHash.h"
#include "Luau/NotNull.h" #include "Luau/NotNull.h"
#include "Luau/Polarity.h"
#include "Luau/Predicate.h" #include "Luau/Predicate.h"
#include "Luau/Refinement.h"
#include "Luau/Unifiable.h" #include "Luau/Unifiable.h"
#include "Luau/Variant.h" #include "Luau/Variant.h"
#include "Luau/VecDeque.h" #include "Luau/VecDeque.h"
@ -19,7 +20,6 @@
#include <optional> #include <optional>
#include <set> #include <set>
#include <string> #include <string>
#include <unordered_map>
#include <vector> #include <vector>
LUAU_FASTINT(LuauTableTypeMaximumStringifierLength) LUAU_FASTINT(LuauTableTypeMaximumStringifierLength)
@ -72,7 +72,7 @@ struct FreeType
// New constructors // New constructors
explicit FreeType(TypeLevel level, TypeId lowerBound, TypeId upperBound); explicit FreeType(TypeLevel level, TypeId lowerBound, TypeId upperBound);
// This one got promoted to explicit // This one got promoted to explicit
explicit FreeType(Scope* scope, TypeId lowerBound, TypeId upperBound); 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 // Old constructors
explicit FreeType(TypeLevel level); explicit FreeType(TypeLevel level);
@ -91,6 +91,8 @@ struct FreeType
// Only used under local type inference // Only used under local type inference
TypeId lowerBound = nullptr; TypeId lowerBound = nullptr;
TypeId upperBound = nullptr; TypeId upperBound = nullptr;
Polarity polarity = Polarity::Unknown;
}; };
struct GenericType struct GenericType
@ -99,8 +101,8 @@ struct GenericType
GenericType(); GenericType();
explicit GenericType(TypeLevel level); explicit GenericType(TypeLevel level);
explicit GenericType(const Name& name); explicit GenericType(const Name& name, Polarity polarity = Polarity::Unknown);
explicit GenericType(Scope* scope); explicit GenericType(Scope* scope, Polarity polarity = Polarity::Unknown);
GenericType(TypeLevel level, const Name& name); GenericType(TypeLevel level, const Name& name);
GenericType(Scope* scope, const Name& name); GenericType(Scope* scope, const Name& name);
@ -110,6 +112,8 @@ struct GenericType
Scope* scope = nullptr; Scope* scope = nullptr;
Name name; Name name;
bool explicitName = false; bool explicitName = false;
Polarity polarity = Polarity::Unknown;
}; };
// When an equality constraint is found, it is then "bound" to that type, // When an equality constraint is found, it is then "bound" to that type,
@ -348,10 +352,8 @@ struct FunctionType
); );
// Local monomorphic function // Local monomorphic function
FunctionType(TypeLevel level, TypePackId argTypes, TypePackId retTypes, std::optional<FunctionDefinition> defn = {}, bool hasSelf = false);
FunctionType( FunctionType(
TypeLevel level, TypeLevel level,
Scope* scope,
TypePackId argTypes, TypePackId argTypes,
TypePackId retTypes, TypePackId retTypes,
std::optional<FunctionDefinition> defn = {}, std::optional<FunctionDefinition> defn = {},
@ -368,16 +370,6 @@ struct FunctionType
std::optional<FunctionDefinition> defn = {}, std::optional<FunctionDefinition> defn = {},
bool hasSelf = false bool hasSelf = false
); );
FunctionType(
TypeLevel level,
Scope* scope,
std::vector<TypeId> generics,
std::vector<TypePackId> genericPacks,
TypePackId argTypes,
TypePackId retTypes,
std::optional<FunctionDefinition> defn = {},
bool hasSelf = false
);
std::optional<FunctionDefinition> definition; std::optional<FunctionDefinition> definition;
/// These should all be generic /// These should all be generic
@ -386,7 +378,6 @@ struct FunctionType
std::vector<std::optional<FunctionArgument>> argNames; std::vector<std::optional<FunctionArgument>> argNames;
Tags tags; Tags tags;
TypeLevel level; TypeLevel level;
Scope* scope = nullptr;
TypePackId argTypes; TypePackId argTypes;
TypePackId retTypes; TypePackId retTypes;
std::shared_ptr<MagicFunction> magic = nullptr; std::shared_ptr<MagicFunction> magic = nullptr;
@ -396,6 +387,7 @@ struct FunctionType
// this flag is used as an optimization to exit early from procedures that manipulate free or generic types. // this flag is used as an optimization to exit early from procedures that manipulate free or generic types.
bool hasNoFreeOrGenericTypes = false; bool hasNoFreeOrGenericTypes = false;
bool isCheckedFunction = false; bool isCheckedFunction = false;
bool isDeprecatedFunction = false;
}; };
enum class TableState enum class TableState
@ -472,7 +464,9 @@ struct Property
TypeId type() const; TypeId type() const;
void setType(TypeId ty); void setType(TypeId ty);
// Sets the write type of this property to the read type. // If this property has a present `writeTy`, set it equal to the `readTy`.
// This is to ensure that if we normalize a property that has divergent
// read and write types, we make them converge (for now).
void makeShared(); void makeShared();
bool isShared() const; bool isShared() const;
@ -517,9 +511,6 @@ struct TableType
std::optional<TypeId> boundTo; std::optional<TypeId> boundTo;
Tags tags; Tags tags;
// Methods of this table that have an untyped self will use the same shared self type.
std::optional<TypeId> selfTy;
// We track the number of as-yet-unadded properties to unsealed tables. // We track the number of as-yet-unadded properties to unsealed tables.
// Some constraints will use this information to decide whether or not they // Some constraints will use this information to decide whether or not they
// are able to dispatch. // are able to dispatch.
@ -622,7 +613,6 @@ struct UserDefinedFunctionData
AstStatTypeFunction* definition = nullptr; AstStatTypeFunction* definition = nullptr;
DenseHashMap<Name, std::pair<AstStatTypeFunction*, size_t>> environment{""}; DenseHashMap<Name, std::pair<AstStatTypeFunction*, size_t>> environment{""};
DenseHashMap<Name, AstStatTypeFunction*> environment_DEPRECATED{""};
}; };
/** /**
@ -882,6 +872,9 @@ struct TypeFun
*/ */
TypeId type; TypeId type;
// The location of where this TypeFun was defined, if available
std::optional<Location> definitionLocation;
TypeFun() = default; TypeFun() = default;
explicit TypeFun(TypeId ty) explicit TypeFun(TypeId ty)
@ -889,16 +882,23 @@ struct TypeFun
{ {
} }
TypeFun(std::vector<GenericTypeDefinition> typeParams, TypeId type) TypeFun(std::vector<GenericTypeDefinition> typeParams, TypeId type, std::optional<Location> definitionLocation = std::nullopt)
: typeParams(std::move(typeParams)) : typeParams(std::move(typeParams))
, type(type) , type(type)
, definitionLocation(definitionLocation)
{ {
} }
TypeFun(std::vector<GenericTypeDefinition> typeParams, std::vector<GenericTypePackDefinition> typePackParams, TypeId type) TypeFun(
std::vector<GenericTypeDefinition> typeParams,
std::vector<GenericTypePackDefinition> typePackParams,
TypeId type,
std::optional<Location> definitionLocation = std::nullopt
)
: typeParams(std::move(typeParams)) : typeParams(std::move(typeParams))
, typePackParams(std::move(typePackParams)) , typePackParams(std::move(typePackParams))
, type(type) , type(type)
, definitionLocation(definitionLocation)
{ {
} }
@ -1202,7 +1202,7 @@ private:
} }
}; };
TypeId freshType(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, Scope* scope); TypeId freshType(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, Scope* scope, Polarity polarity = Polarity::Unknown);
using TypeIdPredicate = std::function<std::optional<TypeId>(TypeId)>; using TypeIdPredicate = std::function<std::optional<TypeId>(TypeId)>;
std::vector<TypeId> filterMap(TypeId type, TypeIdPredicate predicate); std::vector<TypeId> filterMap(TypeId type, TypeIdPredicate predicate);

View file

@ -1,6 +1,7 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once #pragma once
#include "Luau/Polarity.h"
#include "Luau/TypedAllocator.h" #include "Luau/TypedAllocator.h"
#include "Luau/Type.h" #include "Luau/Type.h"
#include "Luau/TypePack.h" #include "Luau/TypePack.h"
@ -40,7 +41,7 @@ struct TypeArena
TypeId freshType_DEPRECATED(Scope* scope); TypeId freshType_DEPRECATED(Scope* scope);
TypeId freshType_DEPRECATED(Scope* scope, TypeLevel level); TypeId freshType_DEPRECATED(Scope* scope, TypeLevel level);
TypePackId freshTypePack(Scope* scope); TypePackId freshTypePack(Scope* scope, Polarity polarity = Polarity::Unknown);
TypePackId addTypePack(std::initializer_list<TypeId> types); TypePackId addTypePack(std::initializer_list<TypeId> types);
TypePackId addTypePack(std::vector<TypeId> types, std::optional<TypePackId> tail = {}); TypePackId addTypePack(std::vector<TypeId> types, std::optional<TypePackId> tail = {});

View file

@ -13,6 +13,8 @@
#include "Luau/TypeOrPack.h" #include "Luau/TypeOrPack.h"
#include "Luau/TypeUtils.h" #include "Luau/TypeUtils.h"
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
namespace Luau namespace Luau
{ {
@ -38,18 +40,29 @@ struct Reasonings
std::string toString() std::string toString()
{ {
if (FFlag::LuauImproveTypePathsInErrors && reasons.empty())
return "";
// DenseHashSet ordering is entirely undefined, so we want to // DenseHashSet ordering is entirely undefined, so we want to
// sort the reasons here to achieve a stable error // sort the reasons here to achieve a stable error
// stringification. // stringification.
std::sort(reasons.begin(), reasons.end()); std::sort(reasons.begin(), reasons.end());
std::string allReasons; std::string allReasons = FFlag::LuauImproveTypePathsInErrors ? "\nthis is because " : "";
bool first = true; bool first = true;
for (const std::string& reason : reasons) for (const std::string& reason : reasons)
{ {
if (first) if (FFlag::LuauImproveTypePathsInErrors)
first = false; {
if (reasons.size() > 1)
allReasons += "\n\t * ";
}
else else
allReasons += "\n\t"; {
if (first)
first = false;
else
allReasons += "\n\t";
}
allReasons += reason; allReasons += reason;
} }

View file

@ -48,6 +48,9 @@ struct TypeFunctionRuntime
// Evaluation of type functions should only be performed in the absence of parse errors in the source module // Evaluation of type functions should only be performed in the absence of parse errors in the source module
bool allowEvaluation = true; bool allowEvaluation = true;
// Root scope in which the type function operates in, set up by ConstraintGenerator
ScopePtr rootScope;
// Output created by 'print' function // Output created by 'print' function
std::vector<std::string> messages; std::vector<std::string> messages;
@ -174,6 +177,7 @@ struct FunctionGraphReductionResult
DenseHashSet<TypePackId> blockedPacks{nullptr}; DenseHashSet<TypePackId> blockedPacks{nullptr};
DenseHashSet<TypeId> reducedTypes{nullptr}; DenseHashSet<TypeId> reducedTypes{nullptr};
DenseHashSet<TypePackId> reducedPacks{nullptr}; DenseHashSet<TypePackId> reducedPacks{nullptr};
DenseHashSet<TypeId> irreducibleTypes{nullptr};
}; };
/** /**
@ -244,6 +248,8 @@ struct BuiltinTypeFunctions
TypeFunction setmetatableFunc; TypeFunction setmetatableFunc;
TypeFunction getmetatableFunc; TypeFunction getmetatableFunc;
TypeFunction weakoptionalFunc;
void addToScope(NotNull<TypeArena> arena, NotNull<Scope> scope) const; void addToScope(NotNull<TypeArena> arena, NotNull<Scope> scope) const;
}; };

View file

@ -223,8 +223,6 @@ struct TypeFunctionClassType
std::optional<TypeFunctionTypeId> writeParent; std::optional<TypeFunctionTypeId> writeParent;
TypeId classTy; TypeId classTy;
std::string name_DEPRECATED;
}; };
struct TypeFunctionGenericType struct TypeFunctionGenericType

View file

@ -28,14 +28,8 @@ struct TypeFunctionRuntimeBuilderState
{ {
NotNull<TypeFunctionContext> ctx; NotNull<TypeFunctionContext> ctx;
// Mapping of class name to ClassType
// Invariant: users can not create a new class types -> any class types that get deserialized must have been an argument to the type function
// Using this invariant, whenever a ClassType is serialized, we can put it into this map
// whenever a ClassType is deserialized, we can use this map to return the corresponding value
DenseHashMap<std::string, TypeId> classesSerialized_DEPRECATED{{}};
// List of errors that occur during serialization/deserialization // List of errors that occur during serialization/deserialization
// At every iteration of serialization/deserialzation, if this list.size() != 0, we halt the process // At every iteration of serialization/deserialization, if this list.size() != 0, we halt the process
std::vector<std::string> errors{}; std::vector<std::string> errors{};
TypeFunctionRuntimeBuilderState(NotNull<TypeFunctionContext> ctx) TypeFunctionRuntimeBuilderState(NotNull<TypeFunctionContext> ctx)

View file

@ -1,11 +1,12 @@
// 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
#pragma once #pragma once
#include "Luau/Common.h"
#include "Luau/NotNull.h"
#include "Luau/Polarity.h"
#include "Luau/TypeFwd.h"
#include "Luau/Unifiable.h" #include "Luau/Unifiable.h"
#include "Luau/Variant.h" #include "Luau/Variant.h"
#include "Luau/TypeFwd.h"
#include "Luau/NotNull.h"
#include "Luau/Common.h"
#include <optional> #include <optional>
#include <set> #include <set>
@ -26,12 +27,14 @@ struct TypeFunctionInstanceTypePack;
struct FreeTypePack struct FreeTypePack
{ {
explicit FreeTypePack(TypeLevel level); explicit FreeTypePack(TypeLevel level);
explicit FreeTypePack(Scope* scope); explicit FreeTypePack(Scope* scope, Polarity polarity = Polarity::Unknown);
FreeTypePack(Scope* scope, TypeLevel level); FreeTypePack(Scope* scope, TypeLevel level);
int index; int index;
TypeLevel level; TypeLevel level;
Scope* scope = nullptr; Scope* scope = nullptr;
Polarity polarity = Polarity::Unknown;
}; };
struct GenericTypePack struct GenericTypePack
@ -40,7 +43,7 @@ struct GenericTypePack
GenericTypePack(); GenericTypePack();
explicit GenericTypePack(TypeLevel level); explicit GenericTypePack(TypeLevel level);
explicit GenericTypePack(const Name& name); explicit GenericTypePack(const Name& name);
explicit GenericTypePack(Scope* scope); explicit GenericTypePack(Scope* scope, Polarity polarity = Polarity::Unknown);
GenericTypePack(TypeLevel level, const Name& name); GenericTypePack(TypeLevel level, const Name& name);
GenericTypePack(Scope* scope, const Name& name); GenericTypePack(Scope* scope, const Name& name);
@ -49,6 +52,8 @@ struct GenericTypePack
Scope* scope = nullptr; Scope* scope = nullptr;
Name name; Name name;
bool explicitName = false; bool explicitName = false;
Polarity polarity = Polarity::Unknown;
}; };
using BoundTypePack = Unifiable::Bound<TypePackId>; using BoundTypePack = Unifiable::Bound<TypePackId>;
@ -100,9 +105,9 @@ struct TypeFunctionInstanceTypePack
struct TypePackVar struct TypePackVar
{ {
explicit TypePackVar(const TypePackVariant& ty); explicit TypePackVar(const TypePackVariant& tp);
explicit TypePackVar(TypePackVariant&& ty); explicit TypePackVar(TypePackVariant&& tp);
TypePackVar(TypePackVariant&& ty, bool persistent); TypePackVar(TypePackVariant&& tp, bool persistent);
bool operator==(const TypePackVar& rhs) const; bool operator==(const TypePackVar& rhs) const;
@ -169,6 +174,7 @@ struct TypePackIterator
private: private:
TypePackId currentTypePack = nullptr; TypePackId currentTypePack = nullptr;
TypePackId tailCycleCheck = nullptr;
const TypePack* tp = nullptr; const TypePack* tp = nullptr;
size_t currentIndex = 0; size_t currentIndex = 0;

View file

@ -42,9 +42,19 @@ struct Property
/// element. /// element.
struct Index struct Index
{ {
enum class Variant
{
Pack,
Union,
Intersection
};
/// The 0-based index to use for the lookup. /// The 0-based index to use for the lookup.
size_t index; size_t index;
/// The sort of thing we're indexing from, this is used in stringifying the type path for errors.
Variant variant;
bool operator==(const Index& other) const; bool operator==(const Index& other) const;
}; };
@ -205,6 +215,9 @@ using Path = TypePath::Path;
/// terribly clear to end users of the Luau type system. /// terribly clear to end users of the Luau type system.
std::string toString(const TypePath::Path& path, bool prefixDot = false); std::string toString(const TypePath::Path& path, bool prefixDot = false);
/// Converts a Path to a human readable string for error reporting.
std::string toStringHuman(const TypePath::Path& path);
std::optional<TypeOrPack> traverse(TypeId root, const Path& path, NotNull<BuiltinTypes> builtinTypes); std::optional<TypeOrPack> traverse(TypeId root, const Path& path, NotNull<BuiltinTypes> builtinTypes);
std::optional<TypeOrPack> traverse(TypePackId root, const Path& path, NotNull<BuiltinTypes> builtinTypes); std::optional<TypeOrPack> traverse(TypePackId root, const Path& path, NotNull<BuiltinTypes> builtinTypes);

View file

@ -289,4 +289,6 @@ std::vector<TypeId> findBlockedArgTypesIn(AstExprCall* expr, NotNull<DenseHashMa
*/ */
void trackInteriorFreeType(Scope* scope, TypeId ty); void trackInteriorFreeType(Scope* scope, TypeId ty);
void trackInteriorFreeTypePack(Scope* scope, TypePackId tp);
} // namespace Luau } // namespace Luau

View file

@ -44,6 +44,12 @@ struct Unifier2
// Mapping from generic type packs to `TypePack`s of free types to be used in instantiation. // Mapping from generic type packs to `TypePack`s of free types to be used in instantiation.
DenseHashMap<TypePackId, TypePackId> genericPackSubstitutions{nullptr}; DenseHashMap<TypePackId, TypePackId> genericPackSubstitutions{nullptr};
// Unification sometimes results in the creation of new free types.
// We collect them here so that other systems can perform necessary
// bookkeeping.
std::vector<TypeId> newFreshTypes;
std::vector<TypePackId> newFreshTypePacks;
int recursionCount = 0; int recursionCount = 0;
int recursionLimit = 0; int recursionLimit = 0;
@ -87,6 +93,9 @@ struct Unifier2
bool unify(const AnyType* subAny, const TableType* superTable); bool unify(const AnyType* subAny, const TableType* superTable);
bool unify(const TableType* subTable, const AnyType* superAny); bool unify(const TableType* subTable, const AnyType* superAny);
bool unify(const MetatableType* subMetatable, const AnyType*);
bool unify(const AnyType*, const MetatableType* superMetatable);
// TODO think about this one carefully. We don't do unions or intersections of type packs // TODO think about this one carefully. We don't do unions or intersections of type packs
bool unify(TypePackId subTp, TypePackId superTp); bool unify(TypePackId subTp, TypePackId superTp);
@ -110,6 +119,9 @@ private:
// Returns true if needle occurs within haystack already. ie if we bound // Returns true if needle occurs within haystack already. ie if we bound
// needle to haystack, would a cyclic TypePack result? // needle to haystack, would a cyclic TypePack result?
OccursCheckResult occursCheck(DenseHashSet<TypePackId>& seen, TypePackId needle, TypePackId haystack); OccursCheckResult occursCheck(DenseHashSet<TypePackId>& seen, TypePackId needle, TypePackId haystack);
TypeId freshType(NotNull<Scope> scope, Polarity polarity);
TypePackId freshTypePack(NotNull<Scope> scope, Polarity polarity);
}; };
} // namespace Luau } // namespace Luau

View file

@ -1,902 +0,0 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/AnyTypeSummary.h"
#include "Luau/BuiltinDefinitions.h"
#include "Luau/Clone.h"
#include "Luau/Common.h"
#include "Luau/Config.h"
#include "Luau/ConstraintGenerator.h"
#include "Luau/ConstraintSolver.h"
#include "Luau/DataFlowGraph.h"
#include "Luau/DcrLogger.h"
#include "Luau/Module.h"
#include "Luau/Parser.h"
#include "Luau/Scope.h"
#include "Luau/StringUtils.h"
#include "Luau/TimeTrace.h"
#include "Luau/ToString.h"
#include "Luau/Transpiler.h"
#include "Luau/TypeArena.h"
#include "Luau/TypeChecker2.h"
#include "Luau/NonStrictTypeChecker.h"
#include "Luau/TypeInfer.h"
#include "Luau/Variant.h"
#include "Luau/VisitType.h"
#include "Luau/TypePack.h"
#include "Luau/TypeOrPack.h"
#include <algorithm>
#include <memory>
#include <chrono>
#include <condition_variable>
#include <exception>
#include <mutex>
#include <stdexcept>
#include <string>
#include <iostream>
#include <stdio.h>
LUAU_FASTFLAGVARIABLE(StudioReportLuauAny2);
LUAU_FASTINTVARIABLE(LuauAnySummaryRecursionLimit, 300);
LUAU_FASTFLAG(DebugLuauMagicTypes);
namespace Luau
{
void AnyTypeSummary::traverse(const Module* module, AstStat* src, NotNull<BuiltinTypes> builtinTypes)
{
visit(findInnerMostScope(src->location, module), src, module, builtinTypes);
}
void AnyTypeSummary::visit(const Scope* scope, AstStat* stat, const Module* module, NotNull<BuiltinTypes> builtinTypes)
{
RecursionLimiter limiter{&recursionCount, FInt::LuauAnySummaryRecursionLimit};
if (auto s = stat->as<AstStatBlock>())
return visit(scope, s, module, builtinTypes);
else if (auto i = stat->as<AstStatIf>())
return visit(scope, i, module, builtinTypes);
else if (auto s = stat->as<AstStatWhile>())
return visit(scope, s, module, builtinTypes);
else if (auto s = stat->as<AstStatRepeat>())
return visit(scope, s, module, builtinTypes);
else if (auto r = stat->as<AstStatReturn>())
return visit(scope, r, module, builtinTypes);
else if (auto e = stat->as<AstStatExpr>())
return visit(scope, e, module, builtinTypes);
else if (auto s = stat->as<AstStatLocal>())
return visit(scope, s, module, builtinTypes);
else if (auto s = stat->as<AstStatFor>())
return visit(scope, s, module, builtinTypes);
else if (auto s = stat->as<AstStatForIn>())
return visit(scope, s, module, builtinTypes);
else if (auto a = stat->as<AstStatAssign>())
return visit(scope, a, module, builtinTypes);
else if (auto a = stat->as<AstStatCompoundAssign>())
return visit(scope, a, module, builtinTypes);
else if (auto f = stat->as<AstStatFunction>())
return visit(scope, f, module, builtinTypes);
else if (auto f = stat->as<AstStatLocalFunction>())
return visit(scope, f, module, builtinTypes);
else if (auto a = stat->as<AstStatTypeAlias>())
return visit(scope, a, module, builtinTypes);
else if (auto s = stat->as<AstStatDeclareGlobal>())
return visit(scope, s, module, builtinTypes);
else if (auto s = stat->as<AstStatDeclareFunction>())
return visit(scope, s, module, builtinTypes);
else if (auto s = stat->as<AstStatDeclareClass>())
return visit(scope, s, module, builtinTypes);
else if (auto s = stat->as<AstStatError>())
return visit(scope, s, module, builtinTypes);
}
void AnyTypeSummary::visit(const Scope* scope, AstStatBlock* block, const Module* module, NotNull<BuiltinTypes> builtinTypes)
{
RecursionCounter counter{&recursionCount};
if (recursionCount >= FInt::LuauAnySummaryRecursionLimit)
return; // don't report
for (AstStat* stat : block->body)
visit(scope, stat, module, builtinTypes);
}
void AnyTypeSummary::visit(const Scope* scope, AstStatIf* ifStatement, const Module* module, NotNull<BuiltinTypes> builtinTypes)
{
if (ifStatement->thenbody)
{
const Scope* thenScope = findInnerMostScope(ifStatement->thenbody->location, module);
visit(thenScope, ifStatement->thenbody, module, builtinTypes);
}
if (ifStatement->elsebody)
{
const Scope* elseScope = findInnerMostScope(ifStatement->elsebody->location, module);
visit(elseScope, ifStatement->elsebody, module, builtinTypes);
}
}
void AnyTypeSummary::visit(const Scope* scope, AstStatWhile* while_, const Module* module, NotNull<BuiltinTypes> builtinTypes)
{
const Scope* whileScope = findInnerMostScope(while_->location, module);
visit(whileScope, while_->body, module, builtinTypes);
}
void AnyTypeSummary::visit(const Scope* scope, AstStatRepeat* repeat, const Module* module, NotNull<BuiltinTypes> builtinTypes)
{
const Scope* repeatScope = findInnerMostScope(repeat->location, module);
visit(repeatScope, repeat->body, module, builtinTypes);
}
void AnyTypeSummary::visit(const Scope* scope, AstStatReturn* ret, const Module* module, NotNull<BuiltinTypes> builtinTypes)
{
const Scope* retScope = findInnerMostScope(ret->location, module);
auto ctxNode = getNode(rootSrc, ret);
bool seenTP = false;
for (auto val : ret->list)
{
if (isAnyCall(retScope, val, module, builtinTypes))
{
TelemetryTypePair types;
types.inferredType = toString(lookupType(val, module, builtinTypes));
TypeInfo ti{Pattern::FuncApp, toString(ctxNode), types};
typeInfo.push_back(ti);
}
if (isAnyCast(retScope, val, module, builtinTypes))
{
if (auto cast = val->as<AstExprTypeAssertion>())
{
TelemetryTypePair types;
types.annotatedType = toString(lookupAnnotation(cast->annotation, module, builtinTypes));
types.inferredType = toString(lookupType(cast->expr, module, builtinTypes));
TypeInfo ti{Pattern::Casts, toString(ctxNode), types};
typeInfo.push_back(ti);
}
}
if (ret->list.size > 1 && !seenTP)
{
if (containsAny(retScope->returnType))
{
seenTP = true;
TelemetryTypePair types;
types.inferredType = toString(retScope->returnType);
TypeInfo ti{Pattern::TypePk, toString(ctxNode), types};
typeInfo.push_back(ti);
}
}
}
}
void AnyTypeSummary::visit(const Scope* scope, AstStatLocal* local, const Module* module, NotNull<BuiltinTypes> builtinTypes)
{
auto ctxNode = getNode(rootSrc, local);
TypePackId values = reconstructTypePack(local->values, module, builtinTypes);
auto [head, tail] = flatten(values);
size_t posn = 0;
for (AstLocal* loc : local->vars)
{
if (local->vars.data[0] == loc && posn < local->values.size)
{
if (loc->annotation)
{
auto annot = lookupAnnotation(loc->annotation, module, builtinTypes);
if (containsAny(annot))
{
TelemetryTypePair types;
types.annotatedType = toString(annot);
types.inferredType = toString(lookupType(local->values.data[posn], module, builtinTypes));
TypeInfo ti{Pattern::VarAnnot, toString(ctxNode), types};
typeInfo.push_back(ti);
}
}
const AstExprTypeAssertion* maybeRequire = local->values.data[posn]->as<AstExprTypeAssertion>();
if (!maybeRequire)
continue;
if (std::min(local->values.size - 1, posn) < head.size())
{
if (isAnyCast(scope, local->values.data[posn], module, builtinTypes))
{
TelemetryTypePair types;
types.inferredType = toString(head[std::min(local->values.size - 1, posn)]);
TypeInfo ti{Pattern::Casts, toString(ctxNode), types};
typeInfo.push_back(ti);
}
}
}
else
{
if (std::min(local->values.size - 1, posn) < head.size())
{
if (loc->annotation)
{
auto annot = lookupAnnotation(loc->annotation, module, builtinTypes);
if (containsAny(annot))
{
TelemetryTypePair types;
types.annotatedType = toString(annot);
types.inferredType = toString(head[std::min(local->values.size - 1, posn)]);
TypeInfo ti{Pattern::VarAnnot, toString(ctxNode), types};
typeInfo.push_back(ti);
}
}
}
else
{
if (tail)
{
if (containsAny(*tail))
{
TelemetryTypePair types;
types.inferredType = toString(*tail);
TypeInfo ti{Pattern::VarAny, toString(ctxNode), types};
typeInfo.push_back(ti);
}
}
}
}
++posn;
}
}
void AnyTypeSummary::visit(const Scope* scope, AstStatFor* for_, const Module* module, NotNull<BuiltinTypes> builtinTypes)
{
const Scope* forScope = findInnerMostScope(for_->location, module);
visit(forScope, for_->body, module, builtinTypes);
}
void AnyTypeSummary::visit(const Scope* scope, AstStatForIn* forIn, const Module* module, NotNull<BuiltinTypes> builtinTypes)
{
const Scope* loopScope = findInnerMostScope(forIn->location, module);
visit(loopScope, forIn->body, module, builtinTypes);
}
void AnyTypeSummary::visit(const Scope* scope, AstStatAssign* assign, const Module* module, NotNull<BuiltinTypes> builtinTypes)
{
auto ctxNode = getNode(rootSrc, assign);
TypePackId values = reconstructTypePack(assign->values, module, builtinTypes);
auto [head, tail] = flatten(values);
size_t posn = 0;
for (AstExpr* var : assign->vars)
{
TypeId tp = lookupType(var, module, builtinTypes);
if (containsAny(tp))
{
TelemetryTypePair types;
types.annotatedType = toString(tp);
auto loc = std::min(assign->vars.size - 1, posn);
if (head.size() >= assign->vars.size && posn < head.size())
{
types.inferredType = toString(head[posn]);
}
else if (loc < head.size())
types.inferredType = toString(head[loc]);
else
types.inferredType = toString(builtinTypes->nilType);
TypeInfo ti{Pattern::Assign, toString(ctxNode), types};
typeInfo.push_back(ti);
}
++posn;
}
for (AstExpr* val : assign->values)
{
if (isAnyCall(scope, val, module, builtinTypes))
{
TelemetryTypePair types;
types.inferredType = toString(lookupType(val, module, builtinTypes));
TypeInfo ti{Pattern::FuncApp, toString(ctxNode), types};
typeInfo.push_back(ti);
}
if (isAnyCast(scope, val, module, builtinTypes))
{
if (auto cast = val->as<AstExprTypeAssertion>())
{
TelemetryTypePair types;
types.annotatedType = toString(lookupAnnotation(cast->annotation, module, builtinTypes));
types.inferredType = toString(lookupType(val, module, builtinTypes));
TypeInfo ti{Pattern::Casts, toString(ctxNode), types};
typeInfo.push_back(ti);
}
}
}
if (tail)
{
if (containsAny(*tail))
{
TelemetryTypePair types;
types.inferredType = toString(*tail);
TypeInfo ti{Pattern::Assign, toString(ctxNode), types};
typeInfo.push_back(ti);
}
}
}
void AnyTypeSummary::visit(const Scope* scope, AstStatCompoundAssign* assign, const Module* module, NotNull<BuiltinTypes> builtinTypes)
{
auto ctxNode = getNode(rootSrc, assign);
TelemetryTypePair types;
types.inferredType = toString(lookupType(assign->value, module, builtinTypes));
types.annotatedType = toString(lookupType(assign->var, module, builtinTypes));
if (module->astTypes.contains(assign->var))
{
if (containsAny(*module->astTypes.find(assign->var)))
{
TypeInfo ti{Pattern::Assign, toString(ctxNode), types};
typeInfo.push_back(ti);
}
}
else if (module->astTypePacks.contains(assign->var))
{
if (containsAny(*module->astTypePacks.find(assign->var)))
{
TypeInfo ti{Pattern::Assign, toString(ctxNode), types};
typeInfo.push_back(ti);
}
}
if (isAnyCall(scope, assign->value, module, builtinTypes))
{
TypeInfo ti{Pattern::FuncApp, toString(ctxNode), types};
typeInfo.push_back(ti);
}
if (isAnyCast(scope, assign->value, module, builtinTypes))
{
if (auto cast = assign->value->as<AstExprTypeAssertion>())
{
types.annotatedType = toString(lookupAnnotation(cast->annotation, module, builtinTypes));
types.inferredType = toString(lookupType(cast->expr, module, builtinTypes));
TypeInfo ti{Pattern::Casts, toString(ctxNode), types};
typeInfo.push_back(ti);
}
}
}
void AnyTypeSummary::visit(const Scope* scope, AstStatFunction* function, const Module* module, NotNull<BuiltinTypes> builtinTypes)
{
TelemetryTypePair types;
types.inferredType = toString(lookupType(function->func, module, builtinTypes));
if (hasVariadicAnys(scope, function->func, module, builtinTypes))
{
TypeInfo ti{Pattern::VarAny, toString(function), types};
typeInfo.push_back(ti);
}
if (hasArgAnys(scope, function->func, module, builtinTypes))
{
TypeInfo ti{Pattern::FuncArg, toString(function), types};
typeInfo.push_back(ti);
}
if (hasAnyReturns(scope, function->func, module, builtinTypes))
{
TypeInfo ti{Pattern::FuncRet, toString(function), types};
typeInfo.push_back(ti);
}
if (function->func->body->body.size > 0)
visit(scope, function->func->body, module, builtinTypes);
}
void AnyTypeSummary::visit(const Scope* scope, AstStatLocalFunction* function, const Module* module, NotNull<BuiltinTypes> builtinTypes)
{
TelemetryTypePair types;
if (hasVariadicAnys(scope, function->func, module, builtinTypes))
{
types.inferredType = toString(lookupType(function->func, module, builtinTypes));
TypeInfo ti{Pattern::VarAny, toString(function), types};
typeInfo.push_back(ti);
}
if (hasArgAnys(scope, function->func, module, builtinTypes))
{
types.inferredType = toString(lookupType(function->func, module, builtinTypes));
TypeInfo ti{Pattern::FuncArg, toString(function), types};
typeInfo.push_back(ti);
}
if (hasAnyReturns(scope, function->func, module, builtinTypes))
{
types.inferredType = toString(lookupType(function->func, module, builtinTypes));
TypeInfo ti{Pattern::FuncRet, toString(function), types};
typeInfo.push_back(ti);
}
if (function->func->body->body.size > 0)
visit(scope, function->func->body, module, builtinTypes);
}
void AnyTypeSummary::visit(const Scope* scope, AstStatTypeAlias* alias, const Module* module, NotNull<BuiltinTypes> builtinTypes)
{
auto ctxNode = getNode(rootSrc, alias);
auto annot = lookupAnnotation(alias->type, module, builtinTypes);
if (containsAny(annot))
{
// no expr => no inference for aliases
TelemetryTypePair types;
types.annotatedType = toString(annot);
TypeInfo ti{Pattern::Alias, toString(ctxNode), types};
typeInfo.push_back(ti);
}
}
void AnyTypeSummary::visit(const Scope* scope, AstStatExpr* expr, const Module* module, NotNull<BuiltinTypes> builtinTypes)
{
auto ctxNode = getNode(rootSrc, expr);
if (isAnyCall(scope, expr->expr, module, builtinTypes))
{
TelemetryTypePair types;
types.inferredType = toString(lookupType(expr->expr, module, builtinTypes));
TypeInfo ti{Pattern::FuncApp, toString(ctxNode), types};
typeInfo.push_back(ti);
}
}
void AnyTypeSummary::visit(const Scope* scope, AstStatDeclareGlobal* declareGlobal, const Module* module, NotNull<BuiltinTypes> builtinTypes) {}
void AnyTypeSummary::visit(const Scope* scope, AstStatDeclareClass* declareClass, const Module* module, NotNull<BuiltinTypes> builtinTypes) {}
void AnyTypeSummary::visit(const Scope* scope, AstStatDeclareFunction* declareFunction, const Module* module, NotNull<BuiltinTypes> builtinTypes) {}
void AnyTypeSummary::visit(const Scope* scope, AstStatError* error, const Module* module, NotNull<BuiltinTypes> builtinTypes) {}
TypeId AnyTypeSummary::checkForFamilyInhabitance(const TypeId instance, const Location location)
{
if (seenTypeFamilyInstances.find(instance))
return instance;
seenTypeFamilyInstances.insert(instance);
return instance;
}
TypeId AnyTypeSummary::lookupType(const AstExpr* expr, const Module* module, NotNull<BuiltinTypes> builtinTypes)
{
const TypeId* ty = module->astTypes.find(expr);
if (ty)
return checkForFamilyInhabitance(follow(*ty), expr->location);
const TypePackId* tp = module->astTypePacks.find(expr);
if (tp)
{
if (auto fst = first(*tp, /*ignoreHiddenVariadics*/ false))
return checkForFamilyInhabitance(*fst, expr->location);
else if (finite(*tp) && size(*tp) == 0)
return checkForFamilyInhabitance(builtinTypes->nilType, expr->location);
}
return builtinTypes->errorRecoveryType();
}
TypePackId AnyTypeSummary::reconstructTypePack(AstArray<AstExpr*> exprs, const Module* module, NotNull<BuiltinTypes> builtinTypes)
{
if (exprs.size == 0)
return arena.addTypePack(TypePack{{}, std::nullopt});
std::vector<TypeId> head;
for (size_t i = 0; i < exprs.size - 1; ++i)
{
head.push_back(lookupType(exprs.data[i], module, builtinTypes));
}
const TypePackId* tail = module->astTypePacks.find(exprs.data[exprs.size - 1]);
if (tail)
return arena.addTypePack(TypePack{std::move(head), follow(*tail)});
else
return arena.addTypePack(TypePack{std::move(head), builtinTypes->errorRecoveryTypePack()});
}
bool AnyTypeSummary::isAnyCall(const Scope* scope, AstExpr* expr, const Module* module, NotNull<BuiltinTypes> builtinTypes)
{
if (auto call = expr->as<AstExprCall>())
{
TypePackId args = reconstructTypePack(call->args, module, builtinTypes);
if (containsAny(args))
return true;
TypeId func = lookupType(call->func, module, builtinTypes);
if (containsAny(func))
return true;
}
return false;
}
bool AnyTypeSummary::hasVariadicAnys(const Scope* scope, AstExprFunction* expr, const Module* module, NotNull<BuiltinTypes> builtinTypes)
{
if (expr->vararg && expr->varargAnnotation)
{
auto annot = lookupPackAnnotation(expr->varargAnnotation, module);
if (annot && containsAny(*annot))
{
return true;
}
}
return false;
}
bool AnyTypeSummary::hasArgAnys(const Scope* scope, AstExprFunction* expr, const Module* module, NotNull<BuiltinTypes> builtinTypes)
{
if (expr->args.size > 0)
{
for (const AstLocal* arg : expr->args)
{
if (arg->annotation)
{
auto annot = lookupAnnotation(arg->annotation, module, builtinTypes);
if (containsAny(annot))
{
return true;
}
}
}
}
return false;
}
bool AnyTypeSummary::hasAnyReturns(const Scope* scope, AstExprFunction* expr, const Module* module, NotNull<BuiltinTypes> builtinTypes)
{
if (!expr->returnAnnotation)
{
return false;
}
for (AstType* ret : expr->returnAnnotation->types)
{
if (containsAny(lookupAnnotation(ret, module, builtinTypes)))
{
return true;
}
}
if (expr->returnAnnotation->tailType)
{
auto annot = lookupPackAnnotation(expr->returnAnnotation->tailType, module);
if (annot && containsAny(*annot))
{
return true;
}
}
return false;
}
bool AnyTypeSummary::isAnyCast(const Scope* scope, AstExpr* expr, const Module* module, NotNull<BuiltinTypes> builtinTypes)
{
if (auto cast = expr->as<AstExprTypeAssertion>())
{
auto annot = lookupAnnotation(cast->annotation, module, builtinTypes);
if (containsAny(annot))
{
return true;
}
}
return false;
}
TypeId AnyTypeSummary::lookupAnnotation(AstType* annotation, const Module* module, NotNull<BuiltinTypes> builtintypes)
{
if (FFlag::DebugLuauMagicTypes)
{
if (auto ref = annotation->as<AstTypeReference>(); ref && ref->parameters.size > 0)
{
if (auto ann = ref->parameters.data[0].type)
{
TypeId argTy = lookupAnnotation(ref->parameters.data[0].type, module, builtintypes);
return follow(argTy);
}
}
}
const TypeId* ty = module->astResolvedTypes.find(annotation);
if (ty)
return checkForTypeFunctionInhabitance(follow(*ty), annotation->location);
else
return checkForTypeFunctionInhabitance(builtintypes->errorRecoveryType(), annotation->location);
}
TypeId AnyTypeSummary::checkForTypeFunctionInhabitance(const TypeId instance, const Location location)
{
if (seenTypeFunctionInstances.find(instance))
return instance;
seenTypeFunctionInstances.insert(instance);
return instance;
}
std::optional<TypePackId> AnyTypeSummary::lookupPackAnnotation(AstTypePack* annotation, const Module* module)
{
const TypePackId* tp = module->astResolvedTypePacks.find(annotation);
if (tp != nullptr)
return {follow(*tp)};
return {};
}
bool AnyTypeSummary::containsAny(TypeId typ)
{
typ = follow(typ);
if (auto t = seen.find(typ); t && !*t)
{
return false;
}
seen[typ] = false;
RecursionCounter counter{&recursionCount};
if (recursionCount >= FInt::LuauAnySummaryRecursionLimit)
{
return false;
}
bool found = false;
if (auto ty = get<AnyType>(typ))
{
found = true;
}
else if (auto ty = get<UnknownType>(typ))
{
found = true;
}
else if (auto ty = get<TableType>(typ))
{
for (auto& [_name, prop] : ty->props)
{
if (FFlag::LuauSolverV2)
{
if (auto newT = follow(prop.readTy))
{
if (containsAny(*newT))
found = true;
}
else if (auto newT = follow(prop.writeTy))
{
if (containsAny(*newT))
found = true;
}
}
else
{
if (containsAny(prop.type()))
found = true;
}
}
}
else if (auto ty = get<IntersectionType>(typ))
{
for (auto part : ty->parts)
{
if (containsAny(part))
{
found = true;
}
}
}
else if (auto ty = get<UnionType>(typ))
{
for (auto option : ty->options)
{
if (containsAny(option))
{
found = true;
}
}
}
else if (auto ty = get<FunctionType>(typ))
{
if (containsAny(ty->argTypes))
found = true;
else if (containsAny(ty->retTypes))
found = true;
}
seen[typ] = found;
return found;
}
bool AnyTypeSummary::containsAny(TypePackId typ)
{
typ = follow(typ);
if (auto t = seen.find(typ); t && !*t)
{
return false;
}
seen[typ] = false;
auto [head, tail] = flatten(typ);
bool found = false;
for (auto tp : head)
{
if (containsAny(tp))
found = true;
}
if (tail)
{
if (auto vtp = get<VariadicTypePack>(tail))
{
if (auto ty = get<AnyType>(follow(vtp->ty)))
{
found = true;
}
}
else if (auto tftp = get<TypeFunctionInstanceTypePack>(tail))
{
for (TypePackId tp : tftp->packArguments)
{
if (containsAny(tp))
{
found = true;
}
}
for (TypeId t : tftp->typeArguments)
{
if (containsAny(t))
{
found = true;
}
}
}
}
seen[typ] = found;
return found;
}
const Scope* AnyTypeSummary::findInnerMostScope(const Location location, const Module* module)
{
const Scope* bestScope = module->getModuleScope().get();
bool didNarrow = false;
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<AstExpr*> AnyTypeSummary::matchRequire(const AstExprCall& call)
{
const char* require = "require";
if (call.args.size != 1)
return std::nullopt;
const AstExprGlobal* funcAsGlobal = call.func->as<AstExprGlobal>();
if (!funcAsGlobal || funcAsGlobal->name != require)
return std::nullopt;
if (call.args.size != 1)
return std::nullopt;
return call.args.data[0];
}
AstNode* AnyTypeSummary::getNode(AstStatBlock* root, AstNode* node)
{
FindReturnAncestry finder(node, root->location.end);
root->visit(&finder);
if (!finder.currNode)
finder.currNode = node;
LUAU_ASSERT(finder.found && finder.currNode);
return finder.currNode;
}
bool AnyTypeSummary::FindReturnAncestry::visit(AstStatLocalFunction* node)
{
currNode = node;
return !found;
}
bool AnyTypeSummary::FindReturnAncestry::visit(AstStatFunction* node)
{
currNode = node;
return !found;
}
bool AnyTypeSummary::FindReturnAncestry::visit(AstType* node)
{
return !found;
}
bool AnyTypeSummary::FindReturnAncestry::visit(AstNode* node)
{
if (node == stat)
{
found = true;
}
if (node->location.end == rootEnd && stat->location.end >= rootEnd)
{
currNode = node;
found = true;
}
return !found;
}
AnyTypeSummary::TypeInfo::TypeInfo(Pattern code, std::string node, TelemetryTypePair type)
: code(code)
, node(node)
, type(type)
{
}
AnyTypeSummary::FindReturnAncestry::FindReturnAncestry(AstNode* stat, Position rootEnd)
: stat(stat)
, rootEnd(rootEnd)
{
}
AnyTypeSummary::AnyTypeSummary() {}
} // namespace Luau

View file

@ -1065,6 +1065,11 @@ struct AstJsonEncoder : public AstVisitor
); );
} }
void write(class AstTypeOptional* node)
{
writeNode(node, "AstTypeOptional", [&]() {});
}
void write(class AstTypeUnion* node) void write(class AstTypeUnion* node)
{ {
writeNode( writeNode(
@ -1146,6 +1151,8 @@ struct AstJsonEncoder : public AstVisitor
return writeString("checked"); return writeString("checked");
case AstAttr::Type::Native: case AstAttr::Type::Native:
return writeString("native"); return writeString("native");
case AstAttr::Type::Deprecated:
return writeString("deprecated");
} }
} }

View file

@ -13,8 +13,6 @@
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauExtendStatEndPosWithSemicolon)
namespace Luau namespace Luau
{ {
@ -43,24 +41,13 @@ struct AutocompleteNodeFinder : public AstVisitor
bool visit(AstStat* stat) override bool visit(AstStat* stat) override
{ {
if (FFlag::LuauExtendStatEndPosWithSemicolon) // Consider 'local myLocal = 4;|' and 'local myLocal = 4', where '|' is the cursor position. In both cases, the cursor position is equal
// to `AstStatLocal.location.end`. However, in the first case (semicolon), we are starting a new statement, whilst in the second case
// (no semicolon) we are still part of the AstStatLocal, hence the different comparison check.
if (stat->location.begin < pos && (stat->hasSemicolon ? pos < stat->location.end : pos <= stat->location.end))
{ {
// Consider 'local myLocal = 4;|' and 'local myLocal = 4', where '|' is the cursor position. In both cases, the cursor position is equal ancestry.push_back(stat);
// to `AstStatLocal.location.end`. However, in the first case (semicolon), we are starting a new statement, whilst in the second case return true;
// (no semicolon) we are still part of the AstStatLocal, hence the different comparison check.
if (stat->location.begin < pos && (stat->hasSemicolon ? pos < stat->location.end : pos <= stat->location.end))
{
ancestry.push_back(stat);
return true;
}
}
else
{
if (stat->location.begin < pos && pos <= stat->location.end)
{
ancestry.push_back(stat);
return true;
}
} }
return false; return false;

View file

@ -23,10 +23,14 @@
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTINT(LuauTypeInferIterationLimit) LUAU_FASTINT(LuauTypeInferIterationLimit)
LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAGVARIABLE(DebugLuauMagicVariableNames)
LUAU_FASTFLAG(LuauExposeRequireByStringAutocomplete)
LUAU_FASTFLAGVARIABLE(LuauAutocompleteRefactorsForIncrementalAutocomplete) LUAU_FASTFLAGVARIABLE(LuauAutocompleteRefactorsForIncrementalAutocomplete)
LUAU_FASTFLAGVARIABLE(LuauAutocompleteUsesModuleForTypeCompatibility) LUAU_FASTFLAGVARIABLE(LuauAutocompleteUsesModuleForTypeCompatibility)
LUAU_FASTFLAGVARIABLE(LuauAutocompleteUnionCopyPreviousSeen)
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"};
@ -481,6 +485,21 @@ static void autocompleteProps(
AutocompleteEntryMap inner; AutocompleteEntryMap inner;
std::unordered_set<TypeId> innerSeen; std::unordered_set<TypeId> innerSeen;
// If we don't do this, and we have the misfortune of receiving a
// recursive union like:
//
// t1 where t1 = t1 | Class
//
// Then we are on a one way journey to a stack overflow.
if (FFlag::LuauAutocompleteUnionCopyPreviousSeen)
{
for (auto ty : seen)
{
if (is<UnionType, IntersectionType>(ty))
innerSeen.insert(ty);
}
}
if (isNil(*iter)) if (isNil(*iter))
{ {
++iter; ++iter;
@ -1343,6 +1362,15 @@ static AutocompleteContext autocompleteExpression(
AstNode* node = ancestry.rbegin()[0]; AstNode* node = ancestry.rbegin()[0];
if (FFlag::DebugLuauMagicVariableNames)
{
InternalErrorReporter ice;
if (auto local = node->as<AstExprLocal>(); local && local->local->name == "_luau_autocomplete_ice")
ice.ice("_luau_autocomplete_ice encountered", local->location);
if (auto global = node->as<AstExprGlobal>(); global && global->name == "_luau_autocomplete_ice")
ice.ice("_luau_autocomplete_ice encountered", global->location);
}
if (node->is<AstExprIndexName>()) if (node->is<AstExprIndexName>())
{ {
if (auto it = module.astTypes.find(node->asExpr())) if (auto it = module.astTypes.find(node->asExpr()))
@ -1509,10 +1537,14 @@ static std::optional<AutocompleteEntryMap> convertRequireSuggestionsToAutocomple
return std::nullopt; return std::nullopt;
AutocompleteEntryMap result; AutocompleteEntryMap result;
for (const RequireSuggestion& suggestion : *suggestions) for (RequireSuggestion& suggestion : *suggestions)
{ {
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);
}
result[std::move(suggestion.label)] = std::move(entry); result[std::move(suggestion.label)] = std::move(entry);
} }
return result; return result;

View file

@ -3,22 +3,23 @@
#include "Luau/Ast.h" #include "Luau/Ast.h"
#include "Luau/Clone.h" #include "Luau/Clone.h"
#include "Luau/Common.h"
#include "Luau/ConstraintGenerator.h"
#include "Luau/ConstraintSolver.h"
#include "Luau/DenseHash.h" #include "Luau/DenseHash.h"
#include "Luau/Error.h" #include "Luau/Error.h"
#include "Luau/Frontend.h" #include "Luau/Frontend.h"
#include "Luau/Symbol.h" #include "Luau/InferPolarity.h"
#include "Luau/Common.h"
#include "Luau/ToString.h"
#include "Luau/ConstraintSolver.h"
#include "Luau/ConstraintGenerator.h"
#include "Luau/NotNull.h" #include "Luau/NotNull.h"
#include "Luau/TypeInfer.h" #include "Luau/Subtyping.h"
#include "Luau/Symbol.h"
#include "Luau/ToString.h"
#include "Luau/Type.h"
#include "Luau/TypeChecker2.h" #include "Luau/TypeChecker2.h"
#include "Luau/TypeFunction.h" #include "Luau/TypeFunction.h"
#include "Luau/TypeInfer.h"
#include "Luau/TypePack.h" #include "Luau/TypePack.h"
#include "Luau/Type.h"
#include "Luau/TypeUtils.h" #include "Luau/TypeUtils.h"
#include "Luau/Subtyping.h"
#include <algorithm> #include <algorithm>
@ -29,11 +30,12 @@
*/ */
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAGVARIABLE(LuauStringFormatErrorSuppression) LUAU_FASTFLAG(LuauNonReentrantGeneralization)
LUAU_FASTFLAGVARIABLE(LuauTableCloneClonesType3) LUAU_FASTFLAGVARIABLE(LuauTableCloneClonesType3)
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope) LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope)
LUAU_FASTFLAGVARIABLE(LuauFreezeIgnorePersistent)
LUAU_FASTFLAGVARIABLE(LuauFollowTableFreeze) LUAU_FASTFLAGVARIABLE(LuauFollowTableFreeze)
LUAU_FASTFLAGVARIABLE(LuauUserTypeFunTypecheck)
LUAU_FASTFLAGVARIABLE(LuauMagicFreezeCheckBlocked)
namespace Luau namespace Luau
{ {
@ -247,6 +249,7 @@ void addGlobalBinding(GlobalTypes& globals, const ScopePtr& scope, const std::st
void addGlobalBinding(GlobalTypes& globals, const ScopePtr& scope, const std::string& name, Binding binding) void addGlobalBinding(GlobalTypes& globals, const ScopePtr& scope, const std::string& name, Binding binding)
{ {
inferGenericPolarities(NotNull{&globals.globalTypes}, NotNull{scope.get()}, binding.typeId);
scope->bindings[globals.globalNames.names->getOrAdd(name.c_str())] = binding; scope->bindings[globals.globalNames.names->getOrAdd(name.c_str())] = binding;
} }
@ -288,6 +291,22 @@ void assignPropDocumentationSymbols(TableType::Props& props, const std::string&
} }
} }
static void finalizeGlobalBindings(ScopePtr scope)
{
LUAU_ASSERT(FFlag::LuauUserTypeFunTypecheck);
for (const auto& pair : scope->bindings)
{
persist(pair.second.typeId);
if (TableType* ttv = getMutable<TableType>(pair.second.typeId))
{
if (!ttv->name)
ttv->name = "typeof(" + toString(pair.first) + ")";
}
}
}
void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeCheckForAutocomplete) void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeCheckForAutocomplete)
{ {
LUAU_ASSERT(!globals.globalTypes.types.isFrozen()); LUAU_ASSERT(!globals.globalTypes.types.isFrozen());
@ -295,6 +314,9 @@ 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
if (FFlag::LuauNonReentrantGeneralization)
globalScope = globals.globalScope.get();
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
builtinTypeFunctions().addToScope(NotNull{&arena}, NotNull{globals.globalScope.get()}); builtinTypeFunctions().addToScope(NotNull{&arena}, NotNull{globals.globalScope.get()});
@ -304,8 +326,8 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
); );
LUAU_ASSERT(loadResult.success); LUAU_ASSERT(loadResult.success);
TypeId genericK = arena.addType(GenericType{"K"}); TypeId genericK = arena.addType(GenericType{globalScope, "K"});
TypeId genericV = arena.addType(GenericType{"V"}); TypeId genericV = arena.addType(GenericType{globalScope, "V"});
TypeId mapOfKtoV = arena.addType(TableType{{}, TableIndexer(genericK, genericV), globals.globalScope->level, TableState::Generic}); TypeId mapOfKtoV = arena.addType(TableType{{}, TableIndexer(genericK, genericV), globals.globalScope->level, TableState::Generic});
std::optional<TypeId> stringMetatableTy = getMetatable(builtinTypes->stringType, builtinTypes); std::optional<TypeId> stringMetatableTy = getMetatable(builtinTypes->stringType, builtinTypes);
@ -353,7 +375,7 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
// pairs<K, V>(t: Table<K, V>) -> ((Table<K, V>, K?) -> (K, V), Table<K, V>, nil) // pairs<K, V>(t: Table<K, V>) -> ((Table<K, V>, K?) -> (K, V), Table<K, V>, nil)
addGlobalBinding(globals, "pairs", arena.addType(FunctionType{{genericK, genericV}, {}, pairsArgsTypePack, pairsReturnTypePack}), "@luau"); addGlobalBinding(globals, "pairs", arena.addType(FunctionType{{genericK, genericV}, {}, pairsArgsTypePack, pairsReturnTypePack}), "@luau");
TypeId genericMT = arena.addType(GenericType{"MT"}); TypeId genericMT = arena.addType(GenericType{globalScope, "MT"});
TableType tab{TableState::Generic, globals.globalScope->level}; TableType tab{TableState::Generic, globals.globalScope->level};
TypeId tabTy = arena.addType(tab); TypeId tabTy = arena.addType(tab);
@ -365,7 +387,7 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
{ {
TypeId genericT = arena.addType(GenericType{"T"}); TypeId genericT = arena.addType(GenericType{globalScope, "T"});
TypeId tMetaMT = arena.addType(MetatableType{genericT, genericMT}); TypeId tMetaMT = arena.addType(MetatableType{genericT, genericMT});
// clang-format off // clang-format off
@ -399,14 +421,21 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
// clang-format on // clang-format on
} }
for (const auto& pair : globals.globalScope->bindings) if (FFlag::LuauUserTypeFunTypecheck)
{ {
persist(pair.second.typeId); finalizeGlobalBindings(globals.globalScope);
}
if (TableType* ttv = getMutable<TableType>(pair.second.typeId)) else
{
for (const auto& pair : globals.globalScope->bindings)
{ {
if (!ttv->name) persist(pair.second.typeId);
ttv->name = "typeof(" + toString(pair.first) + ")";
if (TableType* ttv = getMutable<TableType>(pair.second.typeId))
{
if (!ttv->name)
ttv->name = "typeof(" + toString(pair.first) + ")";
}
} }
} }
@ -415,7 +444,7 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
{ {
// declare function assert<T>(value: T, errorMessage: string?): intersect<T, ~(false?)> // declare function assert<T>(value: T, errorMessage: string?): intersect<T, ~(false?)>
TypeId genericT = arena.addType(GenericType{"T"}); TypeId genericT = arena.addType(GenericType{globalScope, "T"});
TypeId refinedTy = arena.addType(TypeFunctionInstanceType{ TypeId refinedTy = arena.addType(TypeFunctionInstanceType{
NotNull{&builtinTypeFunctions().intersectFunc}, {genericT, arena.addType(NegationType{builtinTypes->falsyType})}, {} NotNull{&builtinTypeFunctions().intersectFunc}, {genericT, arena.addType(NegationType{builtinTypes->falsyType})}, {}
}); });
@ -438,12 +467,16 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
// the top table type. We do the best we can by modelling these // the top table type. We do the best we can by modelling these
// functions using unconstrained generics. It's not quite right, // functions using unconstrained generics. It's not quite right,
// but it'll be ok for now. // but it'll be ok for now.
TypeId genericTy = arena.addType(GenericType{"T"}); TypeId genericTy = arena.addType(GenericType{globalScope, "T"});
TypePackId thePack = arena.addTypePack({genericTy}); TypePackId thePack = arena.addTypePack({genericTy});
TypeId idTyWithMagic = arena.addType(FunctionType{{genericTy}, {}, thePack, thePack}); TypeId idTyWithMagic = arena.addType(FunctionType{{genericTy}, {}, thePack, thePack});
ttv->props["freeze"] = makeProperty(idTyWithMagic, "@luau/global/table.freeze"); ttv->props["freeze"] = makeProperty(idTyWithMagic, "@luau/global/table.freeze");
if (globalScope)
inferGenericPolarities(NotNull{&globals.globalTypes}, NotNull{globalScope}, idTyWithMagic);
TypeId idTy = arena.addType(FunctionType{{genericTy}, {}, thePack, thePack}); TypeId idTy = arena.addType(FunctionType{{genericTy}, {}, thePack, thePack});
if (globalScope)
inferGenericPolarities(NotNull{&globals.globalTypes}, NotNull{globalScope}, idTy);
ttv->props["clone"] = makeProperty(idTy, "@luau/global/table.clone"); ttv->props["clone"] = makeProperty(idTy, "@luau/global/table.clone");
} }
else else
@ -467,6 +500,59 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
TypeId requireTy = getGlobalBinding(globals, "require"); TypeId requireTy = getGlobalBinding(globals, "require");
attachTag(requireTy, kRequireTagName); attachTag(requireTy, kRequireTagName);
attachMagicFunction(requireTy, std::make_shared<MagicRequire>()); attachMagicFunction(requireTy, std::make_shared<MagicRequire>());
if (FFlag::LuauUserTypeFunTypecheck)
{
// Global scope cannot be the parent of the type checking environment because it can be changed by the embedder
globals.globalTypeFunctionScope->exportedTypeBindings = globals.globalScope->exportedTypeBindings;
globals.globalTypeFunctionScope->builtinTypeNames = globals.globalScope->builtinTypeNames;
// Type function runtime also removes a few standard libraries and globals, so we will take only the ones that are defined
static const char* typeFunctionRuntimeBindings[] = {
// Libraries
"math",
"table",
"string",
"bit32",
"utf8",
"buffer",
// Globals
"assert",
"error",
"print",
"next",
"ipairs",
"pairs",
"select",
"unpack",
"getmetatable",
"setmetatable",
"rawget",
"rawset",
"rawlen",
"rawequal",
"tonumber",
"tostring",
"type",
"typeof",
};
for (auto& name : typeFunctionRuntimeBindings)
{
AstName astName = globals.globalNames.names->get(name);
LUAU_ASSERT(astName.value);
globals.globalTypeFunctionScope->bindings[astName] = globals.globalScope->bindings[astName];
}
LoadDefinitionFileResult typeFunctionLoadResult = frontend.loadDefinitionFile(
globals, globals.globalTypeFunctionScope, getTypeFunctionDefinitionSource(), "@luau", /* captureComments */ false, false
);
LUAU_ASSERT(typeFunctionLoadResult.success);
finalizeGlobalBindings(globals.globalTypeFunctionScope);
}
} }
static std::vector<TypeId> parseFormatString(NotNull<BuiltinTypes> builtinTypes, const char* data, size_t size) static std::vector<TypeId> parseFormatString(NotNull<BuiltinTypes> builtinTypes, const char* data, size_t size)
@ -636,25 +722,17 @@ bool MagicFormat::typeCheck(const MagicFunctionTypeCheckContext& context)
if (!result.isSubtype) if (!result.isSubtype)
{ {
if (FFlag::LuauStringFormatErrorSuppression) switch (shouldSuppressErrors(NotNull{&context.typechecker->normalizer}, actualTy))
{
switch (shouldSuppressErrors(NotNull{&context.typechecker->normalizer}, actualTy))
{
case ErrorSuppression::Suppress:
break;
case ErrorSuppression::NormalizationFailed:
break;
case ErrorSuppression::DoNotSuppress:
Reasonings reasonings = context.typechecker->explainReasonings(actualTy, expectedTy, location, result);
if (!reasonings.suppressed)
context.typechecker->reportError(TypeMismatch{expectedTy, actualTy, reasonings.toString()}, location);
}
}
else
{ {
case ErrorSuppression::Suppress:
break;
case ErrorSuppression::NormalizationFailed:
break;
case ErrorSuppression::DoNotSuppress:
Reasonings reasonings = context.typechecker->explainReasonings(actualTy, expectedTy, location, result); Reasonings reasonings = context.typechecker->explainReasonings(actualTy, expectedTy, location, result);
context.typechecker->reportError(TypeMismatch{expectedTy, actualTy, reasonings.toString()}, location);
if (!reasonings.suppressed)
context.typechecker->reportError(TypeMismatch{expectedTy, actualTy, reasonings.toString()}, location);
} }
} }
} }
@ -1444,7 +1522,7 @@ bool MagicClone::infer(const MagicFunctionCallContext& context)
return false; return false;
CloneState cloneState{context.solver->builtinTypes}; CloneState cloneState{context.solver->builtinTypes};
TypeId resultType = shallowClone(inputType, *arena, cloneState, /* ignorePersistent */ FFlag::LuauFreezeIgnorePersistent); TypeId resultType = shallowClone(inputType, *arena, cloneState, /* ignorePersistent */ true);
if (auto tableType = getMutable<TableType>(resultType)) if (auto tableType = getMutable<TableType>(resultType))
{ {
@ -1481,7 +1559,7 @@ static std::optional<TypeId> freezeTable(TypeId inputType, const MagicFunctionCa
{ {
// Clone the input type, this will become our final result type after we mutate it. // Clone the input type, this will become our final result type after we mutate it.
CloneState cloneState{context.solver->builtinTypes}; CloneState cloneState{context.solver->builtinTypes};
TypeId resultType = shallowClone(inputType, *arena, cloneState, /* ignorePersistent */ FFlag::LuauFreezeIgnorePersistent); TypeId resultType = shallowClone(inputType, *arena, cloneState, /* ignorePersistent */ true);
auto tableTy = getMutable<TableType>(resultType); auto tableTy = getMutable<TableType>(resultType);
// `clone` should not break this. // `clone` should not break this.
LUAU_ASSERT(tableTy); LUAU_ASSERT(tableTy);
@ -1531,6 +1609,17 @@ bool MagicFreeze::infer(const MagicFunctionCallContext& context)
std::optional<DefId> resultDef = dfg->getDefOptional(targetExpr); std::optional<DefId> resultDef = dfg->getDefOptional(targetExpr);
std::optional<TypeId> resultTy = resultDef ? scope->lookup(*resultDef) : std::nullopt; std::optional<TypeId> resultTy = resultDef ? scope->lookup(*resultDef) : std::nullopt;
if (FFlag::LuauMagicFreezeCheckBlocked)
{
if (resultTy && !get<BlockedType>(resultTy))
{
// If there's an existing result type but it's _not_ blocked, then
// we aren't type stating this builtin and should fall back to
// regular inference.
return false;
}
}
std::optional<TypeId> frozenType = freezeTable(inputType, context); std::optional<TypeId> frozenType = freezeTable(inputType, context);
if (!frozenType) if (!frozenType)

View file

@ -1,17 +1,20 @@
// 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/Clone.h" #include "Luau/Clone.h"
#include "Luau/Common.h"
#include "Luau/NotNull.h" #include "Luau/NotNull.h"
#include "Luau/Type.h" #include "Luau/Type.h"
#include "Luau/TypePack.h" #include "Luau/TypePack.h"
#include "Luau/Unifiable.h" #include "Luau/Unifiable.h"
#include "Luau/VisitType.h"
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauFreezeIgnorePersistent)
// For each `Luau::clone` call, we will clone only up to N amount of types _and_ packs, as controlled by this limit. // For each `Luau::clone` call, we will clone only up to N amount of types _and_ packs, as controlled by this limit.
LUAU_FASTINTVARIABLE(LuauTypeCloneIterationLimit, 100'000) LUAU_FASTINTVARIABLE(LuauTypeCloneIterationLimit, 100'000)
LUAU_FASTFLAGVARIABLE(LuauClonedTableAndFunctionTypesMustHaveScopes)
LUAU_FASTFLAGVARIABLE(LuauDoNotClonePersistentBindings)
LUAU_FASTFLAG(LuauIncrementalAutocompleteDemandBasedCloning)
namespace Luau namespace Luau
{ {
@ -28,6 +31,8 @@ const T* get(const Kind& kind)
class TypeCloner class TypeCloner
{ {
protected:
NotNull<TypeArena> arena; NotNull<TypeArena> arena;
NotNull<BuiltinTypes> builtinTypes; NotNull<BuiltinTypes> builtinTypes;
@ -62,6 +67,8 @@ public:
{ {
} }
virtual ~TypeCloner() = default;
TypeId clone(TypeId ty) TypeId clone(TypeId ty)
{ {
shallowClone(ty); shallowClone(ty);
@ -120,12 +127,13 @@ private:
} }
} }
protected:
std::optional<TypeId> find(TypeId ty) const std::optional<TypeId> find(TypeId ty) const
{ {
ty = follow(ty, FollowOption::DisableLazyTypeThunks); ty = follow(ty, FollowOption::DisableLazyTypeThunks);
if (auto it = types->find(ty); it != types->end()) if (auto it = types->find(ty); it != types->end())
return it->second; return it->second;
else if (ty->persistent && (!FFlag::LuauFreezeIgnorePersistent || ty != forceTy)) else if (ty->persistent && ty != forceTy)
return ty; return ty;
return std::nullopt; return std::nullopt;
} }
@ -135,7 +143,7 @@ private:
tp = follow(tp); tp = follow(tp);
if (auto it = packs->find(tp); it != packs->end()) if (auto it = packs->find(tp); it != packs->end())
return it->second; return it->second;
else if (tp->persistent && (!FFlag::LuauFreezeIgnorePersistent || tp != forceTp)) else if (tp->persistent && tp != forceTp)
return tp; return tp;
return std::nullopt; return std::nullopt;
} }
@ -154,14 +162,14 @@ private:
} }
public: public:
TypeId shallowClone(TypeId ty) virtual TypeId shallowClone(TypeId ty)
{ {
// We want to [`Luau::follow`] but without forcing the expansion of [`LazyType`]s. // We want to [`Luau::follow`] but without forcing the expansion of [`LazyType`]s.
ty = follow(ty, FollowOption::DisableLazyTypeThunks); ty = follow(ty, FollowOption::DisableLazyTypeThunks);
if (auto clone = find(ty)) if (auto clone = find(ty))
return *clone; return *clone;
else if (ty->persistent && (!FFlag::LuauFreezeIgnorePersistent || ty != forceTy)) else if (ty->persistent && ty != forceTy)
return ty; return ty;
TypeId target = arena->addType(ty->ty); TypeId target = arena->addType(ty->ty);
@ -171,8 +179,6 @@ public:
generic->scope = nullptr; generic->scope = nullptr;
else if (auto free = getMutable<FreeType>(target)) else if (auto free = getMutable<FreeType>(target))
free->scope = nullptr; free->scope = nullptr;
else if (auto fn = getMutable<FunctionType>(target))
fn->scope = nullptr;
else if (auto table = getMutable<TableType>(target)) else if (auto table = getMutable<TableType>(target))
table->scope = nullptr; table->scope = nullptr;
@ -181,13 +187,13 @@ public:
return target; return target;
} }
TypePackId shallowClone(TypePackId tp) virtual TypePackId shallowClone(TypePackId tp)
{ {
tp = follow(tp); tp = follow(tp);
if (auto clone = find(tp)) if (auto clone = find(tp))
return *clone; return *clone;
else if (tp->persistent && (!FFlag::LuauFreezeIgnorePersistent || tp != forceTp)) else if (tp->persistent && tp != forceTp)
return tp; return tp;
TypePackId target = arena->addTypePack(tp->ty); TypePackId target = arena->addTypePack(tp->ty);
@ -389,7 +395,7 @@ private:
ty = shallowClone(ty); ty = shallowClone(ty);
} }
void cloneChildren(LazyType* t) virtual void cloneChildren(LazyType* t)
{ {
if (auto unwrapped = t->unwrapped.load()) if (auto unwrapped = t->unwrapped.load())
t->unwrapped.store(shallowClone(unwrapped)); t->unwrapped.store(shallowClone(unwrapped));
@ -469,11 +475,94 @@ private:
} }
}; };
class FragmentAutocompleteTypeCloner final : public TypeCloner
{
Scope* replacementForNullScope = nullptr;
public:
FragmentAutocompleteTypeCloner(
NotNull<TypeArena> arena,
NotNull<BuiltinTypes> builtinTypes,
NotNull<SeenTypes> types,
NotNull<SeenTypePacks> packs,
TypeId forceTy,
TypePackId forceTp,
Scope* replacementForNullScope
)
: TypeCloner(arena, builtinTypes, types, packs, forceTy, forceTp)
, replacementForNullScope(replacementForNullScope)
{
LUAU_ASSERT(replacementForNullScope);
}
TypeId shallowClone(TypeId ty) override
{
// We want to [`Luau::follow`] but without forcing the expansion of [`LazyType`]s.
ty = follow(ty, FollowOption::DisableLazyTypeThunks);
if (auto clone = find(ty))
return *clone;
else if (ty->persistent && ty != forceTy)
return ty;
TypeId target = arena->addType(ty->ty);
asMutable(target)->documentationSymbol = ty->documentationSymbol;
if (auto generic = getMutable<GenericType>(target))
generic->scope = nullptr;
else if (auto free = getMutable<FreeType>(target))
{
free->scope = replacementForNullScope;
}
else if (auto tt = getMutable<TableType>(target))
{
if (FFlag::LuauClonedTableAndFunctionTypesMustHaveScopes)
tt->scope = replacementForNullScope;
}
(*types)[ty] = target;
queue.emplace_back(target);
return target;
}
TypePackId shallowClone(TypePackId tp) override
{
tp = follow(tp);
if (auto clone = find(tp))
return *clone;
else if (tp->persistent && tp != forceTp)
return tp;
TypePackId target = arena->addTypePack(tp->ty);
if (auto generic = getMutable<GenericTypePack>(target))
generic->scope = nullptr;
else if (auto free = getMutable<FreeTypePack>(target))
free->scope = replacementForNullScope;
(*packs)[tp] = target;
queue.emplace_back(target);
return target;
}
void cloneChildren(LazyType* t) override
{
// Do not clone lazy types
if (!FFlag::LuauIncrementalAutocompleteDemandBasedCloning)
{
if (auto unwrapped = t->unwrapped.load())
t->unwrapped.store(shallowClone(unwrapped));
}
}
};
} // namespace } // namespace
TypePackId shallowClone(TypePackId tp, TypeArena& dest, CloneState& cloneState, bool ignorePersistent) TypePackId shallowClone(TypePackId tp, TypeArena& dest, CloneState& cloneState, bool ignorePersistent)
{ {
if (tp->persistent && (!FFlag::LuauFreezeIgnorePersistent || !ignorePersistent)) if (tp->persistent && !ignorePersistent)
return tp; return tp;
TypeCloner cloner{ TypeCloner cloner{
@ -482,7 +571,7 @@ TypePackId shallowClone(TypePackId tp, TypeArena& dest, CloneState& cloneState,
NotNull{&cloneState.seenTypes}, NotNull{&cloneState.seenTypes},
NotNull{&cloneState.seenTypePacks}, NotNull{&cloneState.seenTypePacks},
nullptr, nullptr,
FFlag::LuauFreezeIgnorePersistent && ignorePersistent ? tp : nullptr ignorePersistent ? tp : nullptr
}; };
return cloner.shallowClone(tp); return cloner.shallowClone(tp);
@ -490,7 +579,7 @@ TypePackId shallowClone(TypePackId tp, TypeArena& dest, CloneState& cloneState,
TypeId shallowClone(TypeId typeId, TypeArena& dest, CloneState& cloneState, bool ignorePersistent) TypeId shallowClone(TypeId typeId, TypeArena& dest, CloneState& cloneState, bool ignorePersistent)
{ {
if (typeId->persistent && (!FFlag::LuauFreezeIgnorePersistent || !ignorePersistent)) if (typeId->persistent && !ignorePersistent)
return typeId; return typeId;
TypeCloner cloner{ TypeCloner cloner{
@ -498,7 +587,7 @@ TypeId shallowClone(TypeId typeId, TypeArena& dest, CloneState& cloneState, bool
cloneState.builtinTypes, cloneState.builtinTypes,
NotNull{&cloneState.seenTypes}, NotNull{&cloneState.seenTypes},
NotNull{&cloneState.seenTypePacks}, NotNull{&cloneState.seenTypePacks},
FFlag::LuauFreezeIgnorePersistent && ignorePersistent ? typeId : nullptr, ignorePersistent ? typeId : nullptr,
nullptr nullptr
}; };
@ -564,4 +653,96 @@ Binding clone(const Binding& binding, TypeArena& dest, CloneState& cloneState)
return b; return b;
} }
TypePackId cloneIncremental(TypePackId tp, TypeArena& dest, CloneState& cloneState, Scope* freshScopeForFreeTypes)
{
if (tp->persistent)
return tp;
FragmentAutocompleteTypeCloner cloner{
NotNull{&dest},
cloneState.builtinTypes,
NotNull{&cloneState.seenTypes},
NotNull{&cloneState.seenTypePacks},
nullptr,
nullptr,
freshScopeForFreeTypes
};
return cloner.clone(tp);
}
TypeId cloneIncremental(TypeId typeId, TypeArena& dest, CloneState& cloneState, Scope* freshScopeForFreeTypes)
{
if (typeId->persistent)
return typeId;
FragmentAutocompleteTypeCloner cloner{
NotNull{&dest},
cloneState.builtinTypes,
NotNull{&cloneState.seenTypes},
NotNull{&cloneState.seenTypePacks},
nullptr,
nullptr,
freshScopeForFreeTypes
};
return cloner.clone(typeId);
}
TypeFun cloneIncremental(const TypeFun& typeFun, TypeArena& dest, CloneState& cloneState, Scope* freshScopeForFreeTypes)
{
FragmentAutocompleteTypeCloner cloner{
NotNull{&dest},
cloneState.builtinTypes,
NotNull{&cloneState.seenTypes},
NotNull{&cloneState.seenTypePacks},
nullptr,
nullptr,
freshScopeForFreeTypes
};
TypeFun copy = typeFun;
for (auto& param : copy.typeParams)
{
param.ty = cloner.clone(param.ty);
if (param.defaultValue)
param.defaultValue = cloner.clone(*param.defaultValue);
}
for (auto& param : copy.typePackParams)
{
param.tp = cloner.clone(param.tp);
if (param.defaultValue)
param.defaultValue = cloner.clone(*param.defaultValue);
}
copy.type = cloner.clone(copy.type);
return copy;
}
Binding cloneIncremental(const Binding& binding, TypeArena& dest, CloneState& cloneState, Scope* freshScopeForFreeTypes)
{
FragmentAutocompleteTypeCloner cloner{
NotNull{&dest},
cloneState.builtinTypes,
NotNull{&cloneState.seenTypes},
NotNull{&cloneState.seenTypePacks},
nullptr,
nullptr,
freshScopeForFreeTypes
};
Binding b;
b.deprecated = binding.deprecated;
b.deprecatedSuggestion = binding.deprecatedSuggestion;
b.documentationSymbol = binding.documentationSymbol;
b.location = binding.location;
b.typeId = FFlag::LuauDoNotClonePersistentBindings && binding.typeId->persistent ? binding.typeId : cloner.clone(binding.typeId);
return b;
}
} // namespace Luau } // namespace Luau

File diff suppressed because it is too large Load diff

View file

@ -35,8 +35,14 @@ LUAU_FASTINTVARIABLE(LuauSolverRecursionLimit, 500)
LUAU_FASTFLAGVARIABLE(DebugLuauEqSatSimplification) LUAU_FASTFLAGVARIABLE(DebugLuauEqSatSimplification)
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope) LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope)
LUAU_FASTFLAGVARIABLE(LuauTrackInteriorFreeTablesOnScope) LUAU_FASTFLAGVARIABLE(LuauTrackInteriorFreeTablesOnScope)
LUAU_FASTFLAGVARIABLE(LuauPrecalculateMutatedFreeTypes2) LUAU_FASTFLAGVARIABLE(LuauHasPropProperBlock)
LUAU_FASTFLAGVARIABLE(DebugLuauGreedyGeneralization) LUAU_FASTFLAGVARIABLE(DebugLuauGreedyGeneralization)
LUAU_FASTFLAG(LuauSearchForRefineableType)
LUAU_FASTFLAG(LuauDeprecatedAttribute)
LUAU_FASTFLAG(LuauNonReentrantGeneralization)
LUAU_FASTFLAG(LuauBidirectionalInferenceCollectIndexerTypes)
LUAU_FASTFLAG(LuauNewTypeFunReductionChecks2)
LUAU_FASTFLAGVARIABLE(LuauTrackInferredFunctionTypeFromCall)
namespace Luau namespace Luau
{ {
@ -356,32 +362,19 @@ ConstraintSolver::ConstraintSolver(
{ {
unsolvedConstraints.emplace_back(c); unsolvedConstraints.emplace_back(c);
if (FFlag::LuauPrecalculateMutatedFreeTypes2) auto maybeMutatedTypesPerConstraint = c->getMaybeMutatedFreeTypes();
for (auto ty : maybeMutatedTypesPerConstraint)
{ {
auto maybeMutatedTypesPerConstraint = c->getMaybeMutatedFreeTypes(); auto [refCount, _] = unresolvedConstraints.try_insert(ty, 0);
for (auto ty : maybeMutatedTypesPerConstraint) refCount += 1;
{
auto [refCount, _] = unresolvedConstraints.try_insert(ty, 0);
refCount += 1;
if (FFlag::DebugLuauGreedyGeneralization) if (FFlag::DebugLuauGreedyGeneralization)
{
auto [it, fresh] = mutatedFreeTypeToConstraint.try_emplace(ty, DenseHashSet<const Constraint*>{nullptr});
it->second.insert(c.get());
}
}
maybeMutatedFreeTypes.emplace(c, maybeMutatedTypesPerConstraint);
}
else
{
// initialize the reference counts for the free types in this constraint.
for (auto ty : c->getMaybeMutatedFreeTypes())
{ {
// increment the reference count for `ty` auto [it, fresh] = mutatedFreeTypeToConstraint.try_emplace(ty, DenseHashSet<const Constraint*>{nullptr});
auto [refCount, _] = unresolvedConstraints.try_insert(ty, 0); it->second.insert(c.get());
refCount += 1;
} }
} }
maybeMutatedFreeTypes.emplace(c, maybeMutatedTypesPerConstraint);
for (NotNull<const Constraint> dep : c->dependencies) for (NotNull<const Constraint> dep : c->dependencies)
@ -472,48 +465,24 @@ void ConstraintSolver::run()
unblock(c); unblock(c);
unsolvedConstraints.erase(unsolvedConstraints.begin() + ptrdiff_t(i)); unsolvedConstraints.erase(unsolvedConstraints.begin() + ptrdiff_t(i));
if (FFlag::LuauPrecalculateMutatedFreeTypes2) const auto maybeMutated = maybeMutatedFreeTypes.find(c);
if (maybeMutated != maybeMutatedFreeTypes.end())
{ {
const auto maybeMutated = maybeMutatedFreeTypes.find(c); DenseHashSet<TypeId> seen{nullptr};
if (maybeMutated != maybeMutatedFreeTypes.end()) for (auto ty : maybeMutated->second)
{ {
DenseHashSet<TypeId> seen{nullptr}; // There is a high chance that this type has been rebound
for (auto ty : maybeMutated->second) // across blocked types, rebound free types, pending
// expansion types, etc, so we need to follow it.
ty = follow(ty);
if (FFlag::DebugLuauGreedyGeneralization)
{ {
// There is a high chance that this type has been rebound if (seen.contains(ty))
// across blocked types, rebound free types, pending continue;
// expansion types, etc, so we need to follow it. seen.insert(ty);
ty = follow(ty);
if (FFlag::DebugLuauGreedyGeneralization)
{
if (seen.contains(ty))
continue;
seen.insert(ty);
}
size_t& refCount = unresolvedConstraints[ty];
if (refCount > 0)
refCount -= 1;
// We have two constraints that are designed to wait for the
// refCount on a free type to be equal to 1: the
// PrimitiveTypeConstraint and ReduceConstraint. We
// therefore wake any constraint waiting for a free type's
// refcount to be 1 or 0.
if (refCount <= 1)
unblock(ty, Location{});
if (FFlag::DebugLuauGreedyGeneralization && refCount == 0)
generalizeOneType(ty);
} }
}
}
else
{
// decrement the referenced free types for this constraint if we dispatched successfully!
for (auto ty : c->getMaybeMutatedFreeTypes())
{
size_t& refCount = unresolvedConstraints[ty]; size_t& refCount = unresolvedConstraints[ty];
if (refCount > 0) if (refCount > 0)
refCount -= 1; refCount -= 1;
@ -525,6 +494,9 @@ void ConstraintSolver::run()
// refcount to be 1 or 0. // refcount to be 1 or 0.
if (refCount <= 1) if (refCount <= 1)
unblock(ty, Location{}); unblock(ty, Location{});
if (FFlag::DebugLuauGreedyGeneralization && refCount == 0)
generalizeOneType(ty);
} }
} }
@ -624,32 +596,30 @@ bool ConstraintSolver::isDone() const
struct TypeSearcher : TypeVisitor struct TypeSearcher : TypeVisitor
{ {
enum struct Polarity: uint8_t
{
None = 0b00,
Positive = 0b01,
Negative = 0b10,
Mixed = 0b11,
};
TypeId needle; TypeId needle;
Polarity current = Polarity::Positive; Polarity current = Polarity::Positive;
size_t count = 0;
Polarity result = Polarity::None; Polarity result = Polarity::None;
explicit TypeSearcher(TypeId needle) explicit TypeSearcher(TypeId needle)
: TypeSearcher(needle, Polarity::Positive) : TypeSearcher(needle, Polarity::Positive)
{} {
}
explicit TypeSearcher(TypeId needle, Polarity initialPolarity) explicit TypeSearcher(TypeId needle, Polarity initialPolarity)
: needle(needle) : needle(needle)
, current(initialPolarity) , current(initialPolarity)
{} {
}
bool visit(TypeId ty) override bool visit(TypeId ty) override
{ {
if (ty == needle) if (ty == needle)
result = Polarity(int(result) | int(current)); {
++count;
result = Polarity(size_t(result) | size_t(current));
}
return true; return true;
} }
@ -658,14 +628,14 @@ struct TypeSearcher : TypeVisitor
{ {
switch (current) switch (current)
{ {
case Polarity::Positive: case Polarity::Positive:
current = Polarity::Negative; current = Polarity::Negative;
break; break;
case Polarity::Negative: case Polarity::Negative:
current = Polarity::Positive; current = Polarity::Positive;
break; break;
default: default:
break; break;
} }
} }
@ -743,23 +713,23 @@ void ConstraintSolver::generalizeOneType(TypeId ty)
switch (ts.result) switch (ts.result)
{ {
case TypeSearcher::Polarity::None: 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}}); asMutable(ty)->reassign(Type{BoundType{upperBound}});
break; break;
case TypeSearcher::Polarity::Negative: case Polarity::Positive:
case TypeSearcher::Polarity::Mixed: if (get<UnknownType>(lowerBound) && ts.count > 1)
if (get<UnknownType>(upperBound))
{
asMutable(ty)->reassign(Type{GenericType{tyScope}});
function->generics.emplace_back(ty);
}
else
asMutable(ty)->reassign(Type{BoundType{upperBound}});
break;
case TypeSearcher::Polarity::Positive:
if (get<UnknownType>(lowerBound))
{ {
asMutable(ty)->reassign(Type{GenericType{tyScope}}); asMutable(ty)->reassign(Type{GenericType{tyScope}});
function->generics.emplace_back(ty); function->generics.emplace_back(ty);
@ -767,6 +737,8 @@ void ConstraintSolver::generalizeOneType(TypeId ty)
else else
asMutable(ty)->reassign(Type{BoundType{lowerBound}}); asMutable(ty)->reassign(Type{BoundType{lowerBound}});
break; break;
default:
LUAU_ASSERT(!"Unreachable");
} }
} }
} }
@ -778,7 +750,16 @@ void ConstraintSolver::bind(NotNull<const Constraint> constraint, TypeId ty, Typ
boundTo = follow(boundTo); boundTo = follow(boundTo);
if (get<BlockedType>(ty) && ty == boundTo) if (get<BlockedType>(ty) && ty == boundTo)
return emplace<FreeType>(constraint, ty, constraint->scope, builtinTypes->neverType, builtinTypes->unknownType); {
emplace<FreeType>(
constraint, ty, constraint->scope, builtinTypes->neverType, builtinTypes->unknownType, Polarity::Mixed
); // FIXME? Is this the right polarity?
if (FFlag::LuauNonReentrantGeneralization)
trackInteriorFreeType(constraint->scope, ty);
return;
}
shiftReferences(ty, boundTo); shiftReferences(ty, boundTo);
emplaceType<BoundType>(asMutable(ty), boundTo); emplaceType<BoundType>(asMutable(ty), boundTo);
@ -903,26 +884,25 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNull<co
else if (get<PendingExpansionType>(generalizedType)) else if (get<PendingExpansionType>(generalizedType))
return block(generalizedType, constraint); return block(generalizedType, constraint);
std::optional<QuantifierResult> generalized;
std::optional<TypeId> generalizedTy = generalize(NotNull{arena}, builtinTypes, constraint->scope, generalizedTypes, c.sourceType); std::optional<TypeId> generalizedTy = generalize(NotNull{arena}, builtinTypes, constraint->scope, generalizedTypes, c.sourceType);
if (generalizedTy) if (!generalizedTy)
generalized = QuantifierResult{*generalizedTy}; // FIXME insertedGenerics and insertedGenericPacks
else
reportError(CodeTooComplex{}, constraint->location); reportError(CodeTooComplex{}, constraint->location);
if (generalized) if (generalizedTy)
{ {
if (get<BlockedType>(generalizedType)) if (get<BlockedType>(generalizedType))
bind(constraint, generalizedType, generalized->result); bind(constraint, generalizedType, *generalizedTy);
else else
unify(constraint, generalizedType, generalized->result); unify(constraint, generalizedType, *generalizedTy);
for (auto [free, gen] : generalized->insertedGenerics.pairings) if (FFlag::LuauDeprecatedAttribute)
unify(constraint, free, gen); {
if (FunctionType* fty = getMutable<FunctionType>(follow(generalizedType)))
for (auto [free, gen] : generalized->insertedGenericPacks.pairings) {
unify(constraint, free, gen); if (c.hasDeprecatedAttribute)
fty->isDeprecatedFunction = true;
}
}
} }
else else
{ {
@ -935,13 +915,53 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNull<co
// 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)
generalize(NotNull{arena}, builtinTypes, constraint->scope, generalizedTypes, ty, /* avoidSealingTables */ false); {
if (FFlag::LuauNonReentrantGeneralization)
{
ty = follow(ty);
if (auto freeTy = get<FreeType>(ty))
{
GeneralizationParams<TypeId> params;
params.foundOutsideFunctions = true;
params.useCount = 1;
params.polarity = freeTy->polarity;
generalizeType(arena, builtinTypes, constraint->scope, ty, params);
}
else if (get<TableType>(ty))
sealTable(constraint->scope, ty);
}
else
generalize(NotNull{arena}, builtinTypes, constraint->scope, generalizedTypes, ty);
}
}
if (FFlag::LuauNonReentrantGeneralization)
{
if (constraint->scope->interiorFreeTypePacks)
{
for (TypePackId tp : *constraint->scope->interiorFreeTypePacks) // NOLINT(bugprone-unchecked-optional-access)
{
tp = follow(tp);
if (auto freeTp = get<FreeTypePack>(tp))
{
GeneralizationParams<TypePackId> params;
params.foundOutsideFunctions = true;
params.useCount = 1;
params.polarity = freeTp->polarity;
LUAU_ASSERT(isKnown(params.polarity));
generalizeTypePack(arena, builtinTypes, constraint->scope, tp, params);
}
}
}
}
} }
else else
{ {
for (TypeId ty : c.interiorTypes) for (TypeId ty : c.interiorTypes)
generalize(NotNull{arena}, builtinTypes, constraint->scope, generalizedTypes, ty, /* avoidSealingTables */ false); generalize(NotNull{arena}, builtinTypes, constraint->scope, generalizedTypes, ty);
} }
@ -1017,8 +1037,8 @@ bool ConstraintSolver::tryDispatch(const IterableConstraint& c, NotNull<const Co
TypeId nextTy = follow(iterator.head[0]); TypeId nextTy = follow(iterator.head[0]);
if (get<FreeType>(nextTy)) if (get<FreeType>(nextTy))
{ {
TypeId keyTy = freshType(arena, builtinTypes, constraint->scope); TypeId keyTy = freshType(arena, builtinTypes, constraint->scope, Polarity::Mixed);
TypeId valueTy = freshType(arena, builtinTypes, constraint->scope); TypeId valueTy = freshType(arena, builtinTypes, constraint->scope, Polarity::Mixed);
if (FFlag::LuauTrackInteriorFreeTypesOnScope) if (FFlag::LuauTrackInteriorFreeTypesOnScope)
{ {
trackInteriorFreeType(constraint->scope, keyTy); trackInteriorFreeType(constraint->scope, keyTy);
@ -1352,15 +1372,29 @@ void ConstraintSolver::fillInDiscriminantTypes(NotNull<const Constraint> constra
if (!ty) if (!ty)
continue; continue;
// If the discriminant type has been transmuted, we need to unblock them. if (FFlag::LuauSearchForRefineableType)
if (!isBlocked(*ty))
{ {
unblock(*ty, constraint->location); if (isBlocked(*ty))
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);
// We bind any unused discriminants to the `*no-refine*` type indicating that it can be safely ignored. // We also need to unconditionally unblock these types, otherwise
emplaceType<BoundType>(asMutable(follow(*ty)), builtinTypes->noRefineType); // you end up with funky looking "Blocked on *no-refine*."
unblock(*ty, constraint->location);
}
else
{
// If the discriminant type has been transmuted, we need to unblock them.
if (!isBlocked(*ty))
{
unblock(*ty, constraint->location);
continue;
}
// We bind any unused discriminants to the `*no-refine*` type indicating that it can be safely ignored.
emplaceType<BoundType>(asMutable(follow(*ty)), builtinTypes->noRefineType);
}
} }
} }
@ -1370,9 +1404,17 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
TypePackId argsPack = follow(c.argsPack); TypePackId argsPack = follow(c.argsPack);
TypePackId result = follow(c.result); TypePackId result = follow(c.result);
if (isBlocked(fn) || hasUnresolvedConstraints(fn)) if (FFlag::DebugLuauGreedyGeneralization)
{ {
return block(c.fn, constraint); if (isBlocked(fn))
return block(c.fn, constraint);
}
else
{
if (isBlocked(fn) || hasUnresolvedConstraints(fn))
{
return block(c.fn, constraint);
}
} }
if (get<AnyType>(fn)) if (get<AnyType>(fn))
@ -1455,7 +1497,8 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
argsPack = arena->addTypePack(TypePack{std::move(argsHead), argsTail}); argsPack = arena->addTypePack(TypePack{std::move(argsHead), argsTail});
fn = follow(*callMm); fn = follow(*callMm);
emplace<FreeTypePack>(constraint, c.result, constraint->scope); emplace<FreeTypePack>(constraint, c.result, constraint->scope, Polarity::Positive);
trackInteriorFreeTypePack(constraint->scope, c.result);
} }
else else
{ {
@ -1472,7 +1515,10 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
} }
if (!usedMagic) if (!usedMagic)
emplace<FreeTypePack>(constraint, c.result, constraint->scope); {
emplace<FreeTypePack>(constraint, c.result, constraint->scope, Polarity::Positive);
trackInteriorFreeTypePack(constraint->scope, c.result);
}
} }
fillInDiscriminantTypes(constraint, c.discriminantTypes); fillInDiscriminantTypes(constraint, c.discriminantTypes);
@ -1493,11 +1539,19 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
if (status == OverloadResolver::Analysis::Ok) if (status == OverloadResolver::Analysis::Ok)
overloadToUse = overload; overloadToUse = overload;
TypeId inferredTy = arena->addType(FunctionType{TypeLevel{}, constraint->scope.get(), argsPack, c.result}); TypeId inferredTy = arena->addType(FunctionType{TypeLevel{}, argsPack, c.result});
Unifier2 u2{NotNull{arena}, builtinTypes, constraint->scope, NotNull{&iceReporter}}; Unifier2 u2{NotNull{arena}, builtinTypes, constraint->scope, NotNull{&iceReporter}};
const bool occursCheckPassed = u2.unify(overloadToUse, inferredTy); const bool occursCheckPassed = u2.unify(overloadToUse, inferredTy);
if (FFlag::LuauNonReentrantGeneralization)
{
for (TypeId freeTy : u2.newFreshTypes)
trackInteriorFreeType(constraint->scope, freeTy);
for (TypePackId freeTp : u2.newFreshTypePacks)
trackInteriorFreeTypePack(constraint->scope, freeTp);
}
if (!u2.genericSubstitutions.empty() || !u2.genericPackSubstitutions.empty()) if (!u2.genericSubstitutions.empty() || !u2.genericPackSubstitutions.empty())
{ {
std::optional<TypePackId> subst = instantiate2(arena, std::move(u2.genericSubstitutions), std::move(u2.genericPackSubstitutions), result); std::optional<TypePackId> subst = instantiate2(arena, std::move(u2.genericSubstitutions), std::move(u2.genericPackSubstitutions), result);
@ -1528,6 +1582,11 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
queuer.traverse(overloadToUse); queuer.traverse(overloadToUse);
queuer.traverse(inferredTy); queuer.traverse(inferredTy);
// This can potentially contain free types if the return type of
// `inferredTy` is never unified elsewhere.
if (FFlag::LuauTrackInteriorFreeTypesOnScope && FFlag::LuauTrackInferredFunctionTypeFromCall)
trackInteriorFreeType(constraint->scope, inferredTy);
unblock(c.result, constraint->location); unblock(c.result, constraint->location);
return true; return true;
@ -1541,6 +1600,43 @@ static AstExpr* unwrapGroup(AstExpr* expr)
return expr; return expr;
} }
struct ContainsGenerics : public TypeOnceVisitor
{
DenseHashSet<const void*> generics{nullptr};
bool found = false;
bool visit(TypeId ty) override
{
return !found;
}
bool visit(TypeId ty, const GenericType&) override
{
found |= generics.contains(ty);
return true;
}
bool visit(TypeId ty, const TypeFunctionInstanceType&) override
{
return !found;
}
bool visit(TypePackId tp, const GenericTypePack&) override
{
found |= generics.contains(tp);
return !found;
}
bool hasGeneric(TypeId ty)
{
traverse(ty);
auto ret = found;
found = false;
return ret;
}
};
bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNull<const Constraint> constraint) bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNull<const Constraint> constraint)
{ {
TypeId fn = follow(c.fn); TypeId fn = follow(c.fn);
@ -1583,36 +1679,49 @@ bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNull<con
DenseHashMap<TypeId, TypeId> replacements{nullptr}; DenseHashMap<TypeId, TypeId> replacements{nullptr};
DenseHashMap<TypePackId, TypePackId> replacementPacks{nullptr}; DenseHashMap<TypePackId, TypePackId> replacementPacks{nullptr};
ContainsGenerics containsGenerics;
for (auto generic : ftv->generics) for (auto generic : ftv->generics)
{
replacements[generic] = builtinTypes->unknownType; replacements[generic] = builtinTypes->unknownType;
if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes)
containsGenerics.generics.insert(generic);
}
for (auto genericPack : ftv->genericPacks) for (auto genericPack : ftv->genericPacks)
{
replacementPacks[genericPack] = builtinTypes->unknownTypePack; replacementPacks[genericPack] = builtinTypes->unknownTypePack;
if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes)
containsGenerics.generics.insert(genericPack);
}
// If the type of the function has generics, we don't actually want to push any of the generics themselves // If the type of the function has generics, we don't actually want to push any of the generics themselves
// into the argument types as expected types because this creates an unnecessary loop. Instead, we want to // into the argument types as expected types because this creates an unnecessary loop. Instead, we want to
// replace these types with `unknown` (and `...unknown`) to keep any structure but not create the cycle. // replace these types with `unknown` (and `...unknown`) to keep any structure but not create the cycle.
if (!replacements.empty() || !replacementPacks.empty()) if (!FFlag::LuauBidirectionalInferenceCollectIndexerTypes)
{ {
Replacer replacer{arena, std::move(replacements), std::move(replacementPacks)}; if (!replacements.empty() || !replacementPacks.empty())
std::optional<TypeId> res = replacer.substitute(fn);
if (res)
{ {
if (*res != fn) Replacer replacer{arena, std::move(replacements), std::move(replacementPacks)};
std::optional<TypeId> res = replacer.substitute(fn);
if (res)
{ {
FunctionType* ftvMut = getMutable<FunctionType>(*res); if (*res != fn)
LUAU_ASSERT(ftvMut); {
ftvMut->generics.clear(); FunctionType* ftvMut = getMutable<FunctionType>(*res);
ftvMut->genericPacks.clear(); LUAU_ASSERT(ftvMut);
ftvMut->generics.clear();
ftvMut->genericPacks.clear();
}
fn = *res;
ftv = get<FunctionType>(*res);
LUAU_ASSERT(ftv);
// we've potentially copied type functions here, so we need to reproduce their reduce constraint.
reproduceConstraints(constraint->scope, constraint->location, replacer);
} }
fn = *res;
ftv = get<FunctionType>(*res);
LUAU_ASSERT(ftv);
// we've potentially copied type functions here, so we need to reproduce their reduce constraint.
reproduceConstraints(constraint->scope, constraint->location, replacer);
} }
} }
@ -1631,6 +1740,10 @@ bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNull<con
(*c.astExpectedTypes)[expr] = expectedArgTy; (*c.astExpectedTypes)[expr] = expectedArgTy;
// Generic types are skipped over entirely, for now.
if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes && containsGenerics.hasGeneric(expectedArgTy))
continue;
const FunctionType* expectedLambdaTy = get<FunctionType>(expectedArgTy); const FunctionType* expectedLambdaTy = get<FunctionType>(expectedArgTy);
const FunctionType* lambdaTy = get<FunctionType>(actualArgTy); const FunctionType* lambdaTy = get<FunctionType>(actualArgTy);
const AstExprFunction* lambdaExpr = expr->as<AstExprFunction>(); const AstExprFunction* lambdaExpr = expr->as<AstExprFunction>();
@ -1658,8 +1771,11 @@ bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNull<con
else if (expr->is<AstExprTable>()) else if (expr->is<AstExprTable>())
{ {
Unifier2 u2{arena, builtinTypes, constraint->scope, NotNull{&iceReporter}}; Unifier2 u2{arena, builtinTypes, constraint->scope, NotNull{&iceReporter}};
Subtyping sp{builtinTypes, arena, simplifier, normalizer, typeFunctionRuntime, NotNull{&iceReporter}};
std::vector<TypeId> toBlock; std::vector<TypeId> toBlock;
(void)matchLiteralType(c.astTypes, c.astExpectedTypes, builtinTypes, arena, NotNull{&u2}, expectedArgTy, actualArgTy, expr, toBlock); (void)matchLiteralType(
c.astTypes, c.astExpectedTypes, builtinTypes, arena, NotNull{&u2}, NotNull{&sp}, expectedArgTy, actualArgTy, expr, toBlock
);
LUAU_ASSERT(toBlock.empty()); LUAU_ASSERT(toBlock.empty());
} }
} }
@ -1683,8 +1799,9 @@ bool ConstraintSolver::tryDispatch(const TableCheckConstraint& c, NotNull<const
return false; return false;
Unifier2 u2{arena, builtinTypes, constraint->scope, NotNull{&iceReporter}}; Unifier2 u2{arena, builtinTypes, constraint->scope, NotNull{&iceReporter}};
Subtyping sp{builtinTypes, arena, simplifier, normalizer, typeFunctionRuntime, NotNull{&iceReporter}};
std::vector<TypeId> toBlock; std::vector<TypeId> toBlock;
(void)matchLiteralType(c.astTypes, c.astExpectedTypes, builtinTypes, arena, NotNull{&u2}, c.expectedType, c.exprType, c.table, toBlock); (void)matchLiteralType(c.astTypes, c.astExpectedTypes, builtinTypes, arena, NotNull{&u2}, NotNull{&sp}, c.expectedType, c.exprType, c.table, toBlock);
LUAU_ASSERT(toBlock.empty()); LUAU_ASSERT(toBlock.empty());
return true; return true;
} }
@ -1731,8 +1848,16 @@ bool ConstraintSolver::tryDispatch(const HasPropConstraint& c, NotNull<const Con
LUAU_ASSERT(get<BlockedType>(resultType)); LUAU_ASSERT(get<BlockedType>(resultType));
LUAU_ASSERT(canMutate(resultType, constraint)); LUAU_ASSERT(canMutate(resultType, constraint));
if (isBlocked(subjectType) || get<PendingExpansionType>(subjectType) || get<TypeFunctionInstanceType>(subjectType)) if (FFlag::LuauHasPropProperBlock)
return block(subjectType, constraint); {
if (isBlocked(subjectType))
return block(subjectType, constraint);
}
else
{
if (isBlocked(subjectType) || get<PendingExpansionType>(subjectType) || get<TypeFunctionInstanceType>(subjectType))
return block(subjectType, constraint);
}
if (const TableType* subjectTable = getTableType(subjectType)) if (const TableType* subjectTable = getTableType(subjectType))
{ {
@ -1794,7 +1919,7 @@ bool ConstraintSolver::tryDispatchHasIndexer(
else if (auto mt = get<MetatableType>(follow(ft->upperBound))) else if (auto mt = get<MetatableType>(follow(ft->upperBound)))
return tryDispatchHasIndexer(recursionDepth, constraint, mt->table, indexType, resultType, seen); return tryDispatchHasIndexer(recursionDepth, constraint, mt->table, indexType, resultType, seen);
FreeType freeResult{ft->scope, builtinTypes->neverType, builtinTypes->unknownType}; FreeType freeResult{ft->scope, builtinTypes->neverType, builtinTypes->unknownType, Polarity::Mixed};
emplace<FreeType>(constraint, resultType, freeResult); emplace<FreeType>(constraint, resultType, freeResult);
TypeId upperBound = TypeId upperBound =
@ -1817,8 +1942,10 @@ bool ConstraintSolver::tryDispatchHasIndexer(
{ {
// FIXME this is greedy. // FIXME this is greedy.
FreeType freeResult{tt->scope, builtinTypes->neverType, builtinTypes->unknownType}; FreeType freeResult{tt->scope, builtinTypes->neverType, builtinTypes->unknownType, Polarity::Mixed};
emplace<FreeType>(constraint, resultType, freeResult); emplace<FreeType>(constraint, resultType, freeResult);
if (FFlag::LuauNonReentrantGeneralization)
trackInteriorFreeType(constraint->scope, resultType);
tt->indexer = TableIndexer{indexType, resultType}; tt->indexer = TableIndexer{indexType, resultType};
return true; return true;
@ -2040,11 +2167,7 @@ bool ConstraintSolver::tryDispatch(const AssignPropConstraint& c, NotNull<const
if (maybeTy) if (maybeTy)
{ {
TypeId propTy = *maybeTy; TypeId propTy = *maybeTy;
bind( bind(constraint, c.propType, isIndex ? arena->addType(UnionType{{propTy, builtinTypes->nilType}}) : propTy);
constraint,
c.propType,
isIndex ? arena->addType(UnionType{{propTy, builtinTypes->nilType}}) : propTy
);
unify(constraint, rhsType, propTy); unify(constraint, rhsType, propTy);
return true; return true;
} }
@ -2138,11 +2261,7 @@ bool ConstraintSolver::tryDispatch(const AssignIndexConstraint& c, NotNull<const
{ {
unify(constraint, indexType, lhsTable->indexer->indexType); unify(constraint, indexType, lhsTable->indexer->indexType);
unify(constraint, rhsType, lhsTable->indexer->indexResultType); unify(constraint, rhsType, lhsTable->indexer->indexResultType);
bind( bind(constraint, c.propType, arena->addType(UnionType{{lhsTable->indexer->indexResultType, builtinTypes->nilType}}));
constraint,
c.propType,
arena->addType(UnionType{{lhsTable->indexer->indexResultType, builtinTypes->nilType}})
);
return true; return true;
} }
@ -2191,11 +2310,7 @@ bool ConstraintSolver::tryDispatch(const AssignIndexConstraint& c, NotNull<const
{ {
unify(constraint, indexType, lhsClass->indexer->indexType); unify(constraint, indexType, lhsClass->indexer->indexType);
unify(constraint, rhsType, lhsClass->indexer->indexResultType); unify(constraint, rhsType, lhsClass->indexer->indexResultType);
bind( bind(constraint, c.propType, arena->addType(UnionType{{lhsClass->indexer->indexResultType, builtinTypes->nilType}}));
constraint,
c.propType,
arena->addType(UnionType{{lhsClass->indexer->indexResultType, builtinTypes->nilType}})
);
return true; return true;
} }
@ -2289,7 +2404,7 @@ bool ConstraintSolver::tryDispatch(const UnpackConstraint& c, NotNull<const Cons
// is only blocked on itself. This doesn't actually // is only blocked on itself. This doesn't actually
// 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); TypeId f = freshType(arena, builtinTypes, constraint->scope, Polarity::Positive); // FIXME? Is this the right polarity?
if (FFlag::LuauTrackInteriorFreeTypesOnScope) if (FFlag::LuauTrackInteriorFreeTypesOnScope)
trackInteriorFreeType(constraint->scope, f); trackInteriorFreeType(constraint->scope, f);
shiftReferences(resultTy, f); shiftReferences(resultTy, f);
@ -2338,11 +2453,18 @@ bool ConstraintSolver::tryDispatch(const ReduceConstraint& c, NotNull<const Cons
for (TypePackId r : result.reducedPacks) for (TypePackId r : result.reducedPacks)
unblock(r, constraint->location); unblock(r, constraint->location);
if (FFlag::LuauNewTypeFunReductionChecks2)
{
for (TypeId ity : result.irreducibleTypes)
uninhabitedTypeFunctions.insert(ity);
}
bool reductionFinished = result.blockedTypes.empty() && result.blockedPacks.empty(); bool reductionFinished = result.blockedTypes.empty() && result.blockedPacks.empty();
ty = follow(ty); ty = follow(ty);
// If we couldn't reduce this type function, stick it in the set! // If we couldn't reduce this type function, stick it in the set!
if (get<TypeFunctionInstanceType>(ty)) if (get<TypeFunctionInstanceType>(ty) && (!FFlag::LuauNewTypeFunReductionChecks2 || !result.irreducibleTypes.find(ty)))
typeFunctionsToFinalize[ty] = constraint; typeFunctionsToFinalize[ty] = constraint;
if (force || reductionFinished) if (force || reductionFinished)
@ -2425,8 +2547,8 @@ bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const Iterabl
if (get<FreeType>(iteratorTy)) if (get<FreeType>(iteratorTy))
{ {
TypeId keyTy = freshType(arena, builtinTypes, constraint->scope); TypeId keyTy = freshType(arena, builtinTypes, constraint->scope, Polarity::Mixed);
TypeId valueTy = freshType(arena, builtinTypes, constraint->scope); TypeId valueTy = freshType(arena, builtinTypes, constraint->scope, Polarity::Mixed);
if (FFlag::LuauTrackInteriorFreeTypesOnScope) if (FFlag::LuauTrackInteriorFreeTypesOnScope)
{ {
trackInteriorFreeType(constraint->scope, keyTy); trackInteriorFreeType(constraint->scope, keyTy);
@ -2687,7 +2809,7 @@ TablePropLookupResult ConstraintSolver::lookupTableProp(
if (ttv->state == TableState::Free) if (ttv->state == TableState::Free)
{ {
TypeId result = freshType(arena, builtinTypes, ttv->scope); TypeId result = freshType(arena, builtinTypes, ttv->scope, Polarity::Mixed);
if (FFlag::LuauTrackInteriorFreeTypesOnScope) if (FFlag::LuauTrackInteriorFreeTypesOnScope)
trackInteriorFreeType(ttv->scope, result); trackInteriorFreeType(ttv->scope, result);
switch (context) switch (context)
@ -2801,7 +2923,7 @@ TablePropLookupResult ConstraintSolver::lookupTableProp(
TableType* tt = getMutable<TableType>(newUpperBound); TableType* tt = getMutable<TableType>(newUpperBound);
LUAU_ASSERT(tt); LUAU_ASSERT(tt);
TypeId propType = freshType(arena, builtinTypes, scope); TypeId propType = freshType(arena, builtinTypes, scope, Polarity::Mixed);
if (FFlag::LuauTrackInteriorFreeTypesOnScope) if (FFlag::LuauTrackInteriorFreeTypesOnScope)
trackInteriorFreeType(scope, propType); trackInteriorFreeType(scope, propType);
@ -3262,7 +3384,7 @@ void ConstraintSolver::shiftReferences(TypeId source, TypeId target)
} }
} }
std::optional<TypeId> ConstraintSolver::generalizeFreeType(NotNull<Scope> scope, TypeId type, bool avoidSealingTables) std::optional<TypeId> ConstraintSolver::generalizeFreeType(NotNull<Scope> scope, TypeId type)
{ {
TypeId t = follow(type); TypeId t = follow(type);
if (get<FreeType>(t)) if (get<FreeType>(t))
@ -3277,7 +3399,7 @@ std::optional<TypeId> ConstraintSolver::generalizeFreeType(NotNull<Scope> scope,
// that until all constraint generation is complete. // that until all constraint generation is complete.
} }
return generalize(NotNull{arena}, builtinTypes, scope, generalizedTypes, type, avoidSealingTables); return generalize(NotNull{arena}, builtinTypes, scope, generalizedTypes, type);
} }
bool ConstraintSolver::hasUnresolvedConstraints(TypeId ty) bool ConstraintSolver::hasUnresolvedConstraints(TypeId ty)

View file

@ -13,32 +13,22 @@
LUAU_FASTFLAG(DebugLuauFreezeArena) LUAU_FASTFLAG(DebugLuauFreezeArena)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAGVARIABLE(LuauPreprocessTypestatedArgument)
LUAU_FASTFLAGVARIABLE(LuauDfgScopeStackTrueReset)
LUAU_FASTFLAGVARIABLE(LuauDfgScopeStackNotNull)
namespace Luau namespace Luau
{ {
bool doesCallError(const AstExprCall* call); // TypeInfer.cpp bool doesCallError(const AstExprCall* call); // TypeInfer.cpp
struct ReferencedDefFinder : public AstVisitor
{
bool visit(AstExprLocal* local) override
{
referencedLocalDefs.push_back(local->local);
return true;
}
// ast defs is just a mapping from expr -> def in general
// will get built up by the dfg builder
// localDefs, we need to copy over
std::vector<AstLocal*> referencedLocalDefs;
};
struct PushScope struct PushScope
{ {
ScopeStack& stack; ScopeStack& stack;
size_t previousSize;
PushScope(ScopeStack& stack, DfgScope* scope) PushScope(ScopeStack& stack, DfgScope* scope)
: stack(stack) : stack(stack)
, previousSize(stack.size())
{ {
// `scope` should never be `nullptr` here. // `scope` should never be `nullptr` here.
LUAU_ASSERT(scope); LUAU_ASSERT(scope);
@ -47,7 +37,18 @@ struct PushScope
~PushScope() ~PushScope()
{ {
stack.pop_back(); if (FFlag::LuauDfgScopeStackTrueReset)
{
// If somehow this stack has _shrunk_ to be smaller than we expect,
// something very strange has happened.
LUAU_ASSERT(stack.size() > previousSize);
while (stack.size() > previousSize)
stack.pop_back();
}
else
{
stack.pop_back();
}
} }
}; };
@ -82,12 +83,6 @@ std::optional<DefId> DataFlowGraph::getDefOptional(const AstExpr* expr) const
return NotNull{*def}; return NotNull{*def};
} }
std::optional<DefId> DataFlowGraph::getRValueDefForCompoundAssign(const AstExpr* expr) const
{
auto def = compoundAssignDefs.find(expr);
return def ? std::optional<DefId>(*def) : std::nullopt;
}
DefId DataFlowGraph::getDef(const AstLocal* local) const DefId DataFlowGraph::getDef(const AstLocal* local) const
{ {
auto def = localDefs.find(local); auto def = localDefs.find(local);
@ -201,7 +196,15 @@ DataFlowGraph DataFlowGraphBuilder::build(
DataFlowGraphBuilder builder(defArena, keyArena); DataFlowGraphBuilder builder(defArena, keyArena);
builder.handle = handle; builder.handle = handle;
DfgScope* moduleScope = builder.makeChildScope();
DfgScope* moduleScope;
// We're not explicitly calling makeChildScope here because that function relies on currentScope
// which guarantees that the scope being returned is NotNull
// This means that while the scope stack is empty, we'll have to manually initialize the global scope
if (FFlag::LuauDfgScopeStackNotNull)
moduleScope = builder.scopes.emplace_back(new DfgScope{nullptr, DfgScope::ScopeType::Linear}).get();
else
moduleScope = builder.makeChildScope();
PushScope ps{builder.scopeStack, moduleScope}; PushScope ps{builder.scopeStack, moduleScope};
builder.visitBlockWithoutChildScope(block); builder.visitBlockWithoutChildScope(block);
builder.resolveCaptures(); builder.resolveCaptures();
@ -233,7 +236,13 @@ void DataFlowGraphBuilder::resolveCaptures()
} }
} }
DfgScope* DataFlowGraphBuilder::currentScope() NotNull<DfgScope> DataFlowGraphBuilder::currentScope()
{
LUAU_ASSERT(!scopeStack.empty());
return NotNull{scopeStack.back()};
}
DfgScope* DataFlowGraphBuilder::currentScope_DEPRECATED()
{ {
if (scopeStack.empty()) if (scopeStack.empty())
return nullptr; // nullptr is the root DFG scope. return nullptr; // nullptr is the root DFG scope.
@ -242,7 +251,10 @@ DfgScope* DataFlowGraphBuilder::currentScope()
DfgScope* DataFlowGraphBuilder::makeChildScope(DfgScope::ScopeType scopeType) DfgScope* DataFlowGraphBuilder::makeChildScope(DfgScope::ScopeType scopeType)
{ {
return scopes.emplace_back(new DfgScope{currentScope(), scopeType}).get(); if (FFlag::LuauDfgScopeStackNotNull)
return scopes.emplace_back(new DfgScope{currentScope(), scopeType}).get();
else
return scopes.emplace_back(new DfgScope{currentScope_DEPRECATED(), scopeType}).get();
} }
void DataFlowGraphBuilder::join(DfgScope* p, DfgScope* a, DfgScope* b) void DataFlowGraphBuilder::join(DfgScope* p, DfgScope* a, DfgScope* b)
@ -317,9 +329,9 @@ void DataFlowGraphBuilder::joinProps(DfgScope* result, const DfgScope& a, const
} }
} }
DefId DataFlowGraphBuilder::lookup(Symbol symbol) DefId DataFlowGraphBuilder::lookup(Symbol symbol, Location location)
{ {
DfgScope* scope = currentScope(); DfgScope* scope = FFlag::LuauDfgScopeStackNotNull ? currentScope() : currentScope_DEPRECATED();
// true if any of the considered scopes are a loop. // true if any of the considered scopes are a loop.
bool outsideLoopScope = false; bool outsideLoopScope = false;
@ -344,15 +356,15 @@ DefId DataFlowGraphBuilder::lookup(Symbol symbol)
} }
} }
DefId result = defArena->freshCell(); DefId result = defArena->freshCell(symbol, location);
scope->bindings[symbol] = result; scope->bindings[symbol] = result;
captures[symbol].allVersions.push_back(result); captures[symbol].allVersions.push_back(result);
return result; return result;
} }
DefId DataFlowGraphBuilder::lookup(DefId def, const std::string& key) DefId DataFlowGraphBuilder::lookup(DefId def, const std::string& key, Location location)
{ {
DfgScope* scope = currentScope(); DfgScope* scope = FFlag::LuauDfgScopeStackNotNull ? currentScope() : currentScope_DEPRECATED();
for (DfgScope* current = scope; current; current = current->parent) for (DfgScope* current = scope; current; current = current->parent)
{ {
if (auto props = current->props.find(def)) if (auto props = current->props.find(def))
@ -362,7 +374,7 @@ DefId DataFlowGraphBuilder::lookup(DefId def, const std::string& key)
} }
else if (auto phi = get<Phi>(def); phi && phi->operands.empty()) // Unresolved phi nodes else if (auto phi = get<Phi>(def); phi && phi->operands.empty()) // Unresolved phi nodes
{ {
DefId result = defArena->freshCell(); DefId result = defArena->freshCell(def->name, location);
scope->props[def][key] = result; scope->props[def][key] = result;
return result; return result;
} }
@ -372,7 +384,7 @@ DefId DataFlowGraphBuilder::lookup(DefId def, const std::string& key)
{ {
std::vector<DefId> defs; std::vector<DefId> defs;
for (DefId operand : phi->operands) for (DefId operand : phi->operands)
defs.push_back(lookup(operand, key)); defs.push_back(lookup(operand, key, location));
DefId result = defArena->phi(defs); DefId result = defArena->phi(defs);
scope->props[def][key] = result; scope->props[def][key] = result;
@ -380,7 +392,7 @@ DefId DataFlowGraphBuilder::lookup(DefId def, const std::string& key)
} }
else if (get<Cell>(def)) else if (get<Cell>(def))
{ {
DefId result = defArena->freshCell(); DefId result = defArena->freshCell(def->name, location);
scope->props[def][key] = result; scope->props[def][key] = result;
return result; return result;
} }
@ -398,7 +410,10 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatBlock* b)
cf = visitBlockWithoutChildScope(b); cf = visitBlockWithoutChildScope(b);
} }
currentScope()->inherit(child); if (FFlag::LuauDfgScopeStackNotNull)
currentScope()->inherit(child);
else
currentScope_DEPRECATED()->inherit(child);
return cf; return cf;
} }
@ -483,7 +498,7 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatIf* i)
elsecf = visit(i->elsebody); elsecf = visit(i->elsebody);
} }
DfgScope* scope = currentScope(); DfgScope* scope = FFlag::LuauDfgScopeStackNotNull ? currentScope() : currentScope_DEPRECATED();
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)
@ -510,7 +525,10 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatWhile* w)
visit(w->body); visit(w->body);
} }
currentScope()->inherit(whileScope); if (FFlag::LuauDfgScopeStackNotNull)
currentScope()->inherit(whileScope);
else
currentScope_DEPRECATED()->inherit(whileScope);
return ControlFlow::None; return ControlFlow::None;
} }
@ -526,7 +544,10 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatRepeat* r)
visitExpr(r->condition); visitExpr(r->condition);
} }
currentScope()->inherit(repeatScope); if (FFlag::LuauDfgScopeStackNotNull)
currentScope()->inherit(repeatScope);
else
currentScope_DEPRECATED()->inherit(repeatScope);
return ControlFlow::None; return ControlFlow::None;
} }
@ -575,7 +596,7 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatLocal* l)
// We need to create a new def to intentionally avoid alias tracking, but we'd like to // We need to create a new def to intentionally avoid alias tracking, but we'd like to
// make sure that the non-aliased defs are also marked as a subscript for refinements. // make sure that the non-aliased defs are also marked as a subscript for refinements.
bool subscripted = i < defs.size() && containsSubscriptedDefinition(defs[i]); bool subscripted = i < defs.size() && containsSubscriptedDefinition(defs[i]);
DefId def = defArena->freshCell(subscripted); DefId def = defArena->freshCell(local, local->location, subscripted);
if (i < l->values.size) if (i < l->values.size)
{ {
AstExpr* e = l->values.data[i]; AstExpr* e = l->values.data[i];
@ -585,7 +606,10 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatLocal* l)
} }
} }
graph.localDefs[local] = def; graph.localDefs[local] = def;
currentScope()->bindings[local] = def; if (FFlag::LuauDfgScopeStackNotNull)
currentScope()->bindings[local] = def;
else
currentScope_DEPRECATED()->bindings[local] = def;
captures[local].allVersions.push_back(def); captures[local].allVersions.push_back(def);
} }
@ -607,16 +631,22 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatFor* f)
if (f->var->annotation) if (f->var->annotation)
visitType(f->var->annotation); visitType(f->var->annotation);
DefId def = defArena->freshCell(); DefId def = defArena->freshCell(f->var, f->var->location);
graph.localDefs[f->var] = def; graph.localDefs[f->var] = def;
currentScope()->bindings[f->var] = def; if (FFlag::LuauDfgScopeStackNotNull)
currentScope()->bindings[f->var] = def;
else
currentScope_DEPRECATED()->bindings[f->var] = def;
captures[f->var].allVersions.push_back(def); captures[f->var].allVersions.push_back(def);
// TODO(controlflow): entry point has a back edge from exit point // TODO(controlflow): entry point has a back edge from exit point
visit(f->body); visit(f->body);
} }
currentScope()->inherit(forScope); if (FFlag::LuauDfgScopeStackNotNull)
currentScope()->inherit(forScope);
else
currentScope_DEPRECATED()->inherit(forScope);
return ControlFlow::None; return ControlFlow::None;
} }
@ -633,9 +663,12 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatForIn* f)
if (local->annotation) if (local->annotation)
visitType(local->annotation); visitType(local->annotation);
DefId def = defArena->freshCell(); DefId def = defArena->freshCell(local, local->location);
graph.localDefs[local] = def; graph.localDefs[local] = def;
currentScope()->bindings[local] = def; if (FFlag::LuauDfgScopeStackNotNull)
currentScope()->bindings[local] = def;
else
currentScope_DEPRECATED()->bindings[local] = def;
captures[local].allVersions.push_back(def); captures[local].allVersions.push_back(def);
} }
@ -646,8 +679,10 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatForIn* f)
visit(f->body); visit(f->body);
} }
if (FFlag::LuauDfgScopeStackNotNull)
currentScope()->inherit(forScope); currentScope()->inherit(forScope);
else
currentScope_DEPRECATED()->inherit(forScope);
return ControlFlow::None; return ControlFlow::None;
} }
@ -662,7 +697,7 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatAssign* a)
for (size_t i = 0; i < a->vars.size; ++i) for (size_t i = 0; i < a->vars.size; ++i)
{ {
AstExpr* v = a->vars.data[i]; AstExpr* v = a->vars.data[i];
visitLValue(v, i < defs.size() ? defs[i] : defArena->freshCell()); visitLValue(v, i < defs.size() ? defs[i] : defArena->freshCell(Symbol{}, v->location));
} }
return ControlFlow::None; return ControlFlow::None;
@ -688,7 +723,7 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatFunction* f)
// //
// which is evidence that references to variables must be a phi node of all possible definitions, // which is evidence that references to variables must be a phi node of all possible definitions,
// but for bug compatibility, we'll assume the same thing here. // but for bug compatibility, we'll assume the same thing here.
visitLValue(f->name, defArena->freshCell()); visitLValue(f->name, defArena->freshCell(Symbol{}, f->name->location));
visitExpr(f->func); visitExpr(f->func);
if (auto local = f->name->as<AstExprLocal>()) if (auto local = f->name->as<AstExprLocal>())
@ -708,9 +743,12 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatFunction* f)
ControlFlow DataFlowGraphBuilder::visit(AstStatLocalFunction* l) ControlFlow DataFlowGraphBuilder::visit(AstStatLocalFunction* l)
{ {
DefId def = defArena->freshCell(); DefId def = defArena->freshCell(l->name, l->location);
graph.localDefs[l->name] = def; graph.localDefs[l->name] = def;
currentScope()->bindings[l->name] = def; if (FFlag::LuauDfgScopeStackNotNull)
currentScope()->bindings[l->name] = def;
else
currentScope_DEPRECATED()->bindings[l->name] = def;
captures[l->name].allVersions.push_back(def); captures[l->name].allVersions.push_back(def);
visitExpr(l->func); visitExpr(l->func);
@ -741,9 +779,12 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatTypeFunction* f)
ControlFlow DataFlowGraphBuilder::visit(AstStatDeclareGlobal* d) ControlFlow DataFlowGraphBuilder::visit(AstStatDeclareGlobal* d)
{ {
DefId def = defArena->freshCell(); DefId def = defArena->freshCell(d->name, d->nameLocation);
graph.declaredDefs[d] = def; graph.declaredDefs[d] = def;
currentScope()->bindings[d->name] = def; if (FFlag::LuauDfgScopeStackNotNull)
currentScope()->bindings[d->name] = def;
else
currentScope_DEPRECATED()->bindings[d->name] = def;
captures[d->name].allVersions.push_back(def); captures[d->name].allVersions.push_back(def);
visitType(d->type); visitType(d->type);
@ -753,9 +794,12 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatDeclareGlobal* d)
ControlFlow DataFlowGraphBuilder::visit(AstStatDeclareFunction* d) ControlFlow DataFlowGraphBuilder::visit(AstStatDeclareFunction* d)
{ {
DefId def = defArena->freshCell(); DefId def = defArena->freshCell(d->name, d->nameLocation);
graph.declaredDefs[d] = def; graph.declaredDefs[d] = def;
currentScope()->bindings[d->name] = def; if (FFlag::LuauDfgScopeStackNotNull)
currentScope()->bindings[d->name] = def;
else
currentScope_DEPRECATED()->bindings[d->name] = def;
captures[d->name].allVersions.push_back(def); captures[d->name].allVersions.push_back(def);
DfgScope* unreachable = makeChildScope(); DfgScope* unreachable = makeChildScope();
@ -810,19 +854,19 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExpr* e)
if (auto g = e->as<AstExprGroup>()) if (auto g = e->as<AstExprGroup>())
return visitExpr(g); return visitExpr(g);
else if (auto c = e->as<AstExprConstantNil>()) else if (auto c = e->as<AstExprConstantNil>())
return {defArena->freshCell(), nullptr}; // ok return {defArena->freshCell(Symbol{}, c->location), nullptr}; // ok
else if (auto c = e->as<AstExprConstantBool>()) else if (auto c = e->as<AstExprConstantBool>())
return {defArena->freshCell(), nullptr}; // ok return {defArena->freshCell(Symbol{}, c->location), nullptr}; // ok
else if (auto c = e->as<AstExprConstantNumber>()) else if (auto c = e->as<AstExprConstantNumber>())
return {defArena->freshCell(), nullptr}; // ok return {defArena->freshCell(Symbol{}, c->location), nullptr}; // ok
else if (auto c = e->as<AstExprConstantString>()) else if (auto c = e->as<AstExprConstantString>())
return {defArena->freshCell(), nullptr}; // ok return {defArena->freshCell(Symbol{}, c->location), nullptr}; // ok
else if (auto l = e->as<AstExprLocal>()) else if (auto l = e->as<AstExprLocal>())
return visitExpr(l); return visitExpr(l);
else if (auto g = e->as<AstExprGlobal>()) else if (auto g = e->as<AstExprGlobal>())
return visitExpr(g); return visitExpr(g);
else if (auto v = e->as<AstExprVarargs>()) else if (auto v = e->as<AstExprVarargs>())
return {defArena->freshCell(), nullptr}; // ok return {defArena->freshCell(Symbol{}, v->location), nullptr}; // ok
else if (auto c = e->as<AstExprCall>()) else if (auto c = e->as<AstExprCall>())
return visitExpr(c); return visitExpr(c);
else if (auto i = e->as<AstExprIndexName>()) else if (auto i = e->as<AstExprIndexName>())
@ -863,14 +907,14 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprGroup* group)
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprLocal* l) DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprLocal* l)
{ {
DefId def = lookup(l->local); DefId def = lookup(l->local, l->local->location);
const RefinementKey* key = keyArena->leaf(def); const RefinementKey* key = keyArena->leaf(def);
return {def, key}; return {def, key};
} }
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprGlobal* g) DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprGlobal* g)
{ {
DefId def = lookup(g->name); DefId def = lookup(g->name, g->location);
return {def, keyArena->leaf(def)}; return {def, keyArena->leaf(def)};
} }
@ -878,6 +922,12 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprCall* c)
{ {
visitExpr(c->func); visitExpr(c->func);
if (FFlag::LuauPreprocessTypestatedArgument)
{
for (AstExpr* arg : c->args)
visitExpr(arg);
}
if (shouldTypestateForFirstArgument(*c) && c->args.size > 1 && isLValue(*c->args.begin())) if (shouldTypestateForFirstArgument(*c) && c->args.size > 1 && isLValue(*c->args.begin()))
{ {
AstExpr* firstArg = *c->args.begin(); AstExpr* firstArg = *c->args.begin();
@ -908,11 +958,23 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprCall* c)
visitLValue(firstArg, def); visitLValue(firstArg, def);
} }
for (AstExpr* arg : c->args) if (!FFlag::LuauPreprocessTypestatedArgument)
visitExpr(arg); {
for (AstExpr* arg : c->args)
visitExpr(arg);
}
// calls should be treated as subscripted. // We treat function calls as "subscripted" as they could potentially
return {defArena->freshCell(/* subscripted */ true), nullptr}; // return a subscripted value, consider:
//
// local function foo(tbl: {[string]: woof)
// return tbl["foobarbaz"]
// end
//
// local v = foo({})
//
// We want to consider `v` to be subscripted here.
return {defArena->freshCell(Symbol{}, c->location, /*subscripted=*/true)};
} }
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprIndexName* i) DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprIndexName* i)
@ -920,7 +982,7 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprIndexName* i)
auto [parentDef, parentKey] = visitExpr(i->expr); auto [parentDef, parentKey] = visitExpr(i->expr);
std::string index = i->index.value; std::string index = i->index.value;
DefId def = lookup(parentDef, index); DefId def = lookup(parentDef, index, i->location);
return {def, keyArena->node(parentKey, def, index)}; return {def, keyArena->node(parentKey, def, index)};
} }
@ -933,11 +995,11 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprIndexExpr* i)
{ {
std::string index{string->value.data, string->value.size}; std::string index{string->value.data, string->value.size};
DefId def = lookup(parentDef, index); DefId def = lookup(parentDef, index, i->location);
return {def, keyArena->node(parentKey, def, index)}; return {def, keyArena->node(parentKey, def, index)};
} }
return {defArena->freshCell(/* subscripted= */ true), nullptr}; return {defArena->freshCell(Symbol{}, i->location, /* subscripted= */ true), nullptr};
} }
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprFunction* f) DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprFunction* f)
@ -950,7 +1012,7 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprFunction* f)
// There's no syntax for `self` to have an annotation if using `function t:m()` // There's no syntax for `self` to have an annotation if using `function t:m()`
LUAU_ASSERT(!self->annotation); LUAU_ASSERT(!self->annotation);
DefId def = defArena->freshCell(); DefId def = defArena->freshCell(f->debugname, f->location);
graph.localDefs[self] = def; graph.localDefs[self] = def;
signatureScope->bindings[self] = def; signatureScope->bindings[self] = def;
captures[self].allVersions.push_back(def); captures[self].allVersions.push_back(def);
@ -961,7 +1023,7 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprFunction* f)
if (param->annotation) if (param->annotation)
visitType(param->annotation); visitType(param->annotation);
DefId def = defArena->freshCell(); DefId def = defArena->freshCell(param, param->location);
graph.localDefs[param] = def; graph.localDefs[param] = def;
signatureScope->bindings[param] = def; signatureScope->bindings[param] = def;
captures[param].allVersions.push_back(def); captures[param].allVersions.push_back(def);
@ -983,13 +1045,16 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprFunction* f)
// g() --> 5 // g() --> 5
visit(f->body); visit(f->body);
return {defArena->freshCell(), nullptr}; return {defArena->freshCell(f->debugname, f->location), nullptr};
} }
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprTable* t) DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprTable* t)
{ {
DefId tableCell = defArena->freshCell(); DefId tableCell = defArena->freshCell(Symbol{}, t->location);
currentScope()->props[tableCell] = {}; if (FFlag::LuauDfgScopeStackNotNull)
currentScope()->props[tableCell] = {};
else
currentScope_DEPRECATED()->props[tableCell] = {};
for (AstExprTable::Item item : t->items) for (AstExprTable::Item item : t->items)
{ {
DataFlowResult result = visitExpr(item.value); DataFlowResult result = visitExpr(item.value);
@ -997,7 +1062,12 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprTable* t)
{ {
visitExpr(item.key); visitExpr(item.key);
if (auto string = item.key->as<AstExprConstantString>()) if (auto string = item.key->as<AstExprConstantString>())
currentScope()->props[tableCell][string->value.data] = result.def; {
if (FFlag::LuauDfgScopeStackNotNull)
currentScope()->props[tableCell][string->value.data] = result.def;
else
currentScope_DEPRECATED()->props[tableCell][string->value.data] = result.def;
}
} }
} }
@ -1008,7 +1078,7 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprUnary* u)
{ {
visitExpr(u->expr); visitExpr(u->expr);
return {defArena->freshCell(), nullptr}; return {defArena->freshCell(Symbol{}, u->location), nullptr};
} }
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprBinary* b) DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprBinary* b)
@ -1016,7 +1086,7 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprBinary* b)
visitExpr(b->left); visitExpr(b->left);
visitExpr(b->right); visitExpr(b->right);
return {defArena->freshCell(), nullptr}; return {defArena->freshCell(Symbol{}, b->location), nullptr};
} }
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprTypeAssertion* t) DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprTypeAssertion* t)
@ -1033,7 +1103,7 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprIfElse* i)
visitExpr(i->trueExpr); visitExpr(i->trueExpr);
visitExpr(i->falseExpr); visitExpr(i->falseExpr);
return {defArena->freshCell(), nullptr}; return {defArena->freshCell(Symbol{}, i->location), nullptr};
} }
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprInterpString* i) DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprInterpString* i)
@ -1041,7 +1111,7 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprInterpString* i)
for (AstExpr* e : i->expressions) for (AstExpr* e : i->expressions)
visitExpr(e); visitExpr(e);
return {defArena->freshCell(), nullptr}; return {defArena->freshCell(Symbol{}, i->location), nullptr};
} }
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprError* error) DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprError* error)
@ -1052,7 +1122,7 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprError* error)
for (AstExpr* e : error->expressions) for (AstExpr* e : error->expressions)
visitExpr(e); visitExpr(e);
return {defArena->freshCell(), nullptr}; return {defArena->freshCell(Symbol{}, error->location), nullptr};
} }
void DataFlowGraphBuilder::visitLValue(AstExpr* e, DefId incomingDef) void DataFlowGraphBuilder::visitLValue(AstExpr* e, DefId incomingDef)
@ -1078,12 +1148,12 @@ void DataFlowGraphBuilder::visitLValue(AstExpr* e, DefId incomingDef)
DefId DataFlowGraphBuilder::visitLValue(AstExprLocal* l, DefId incomingDef) DefId DataFlowGraphBuilder::visitLValue(AstExprLocal* l, DefId incomingDef)
{ {
DfgScope* scope = currentScope(); 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))
{ {
DefId updated = defArena->freshCell(containsSubscriptedDefinition(incomingDef)); DefId updated = defArena->freshCell(l->local, l->location, containsSubscriptedDefinition(incomingDef));
scope->bindings[l->local] = updated; scope->bindings[l->local] = updated;
captures[l->local].allVersions.push_back(updated); captures[l->local].allVersions.push_back(updated);
return updated; return updated;
@ -1094,12 +1164,12 @@ DefId DataFlowGraphBuilder::visitLValue(AstExprLocal* l, DefId incomingDef)
DefId DataFlowGraphBuilder::visitLValue(AstExprGlobal* g, DefId incomingDef) DefId DataFlowGraphBuilder::visitLValue(AstExprGlobal* g, DefId incomingDef)
{ {
DfgScope* scope = currentScope(); 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(g->name)) if (scope->canUpdateDefinition(g->name))
{ {
DefId updated = defArena->freshCell(containsSubscriptedDefinition(incomingDef)); DefId updated = defArena->freshCell(g->name, g->location, containsSubscriptedDefinition(incomingDef));
scope->bindings[g->name] = updated; scope->bindings[g->name] = updated;
captures[g->name].allVersions.push_back(updated); captures[g->name].allVersions.push_back(updated);
return updated; return updated;
@ -1112,10 +1182,10 @@ DefId DataFlowGraphBuilder::visitLValue(AstExprIndexName* i, DefId incomingDef)
{ {
DefId parentDef = visitExpr(i->expr).def; DefId parentDef = visitExpr(i->expr).def;
DfgScope* scope = currentScope(); DfgScope* scope = FFlag::LuauDfgScopeStackNotNull ? currentScope() : currentScope_DEPRECATED();
if (scope->canUpdateDefinition(parentDef, i->index.value)) if (scope->canUpdateDefinition(parentDef, i->index.value))
{ {
DefId updated = defArena->freshCell(containsSubscriptedDefinition(incomingDef)); DefId updated = defArena->freshCell(i->index, i->location, containsSubscriptedDefinition(incomingDef));
scope->props[parentDef][i->index.value] = updated; scope->props[parentDef][i->index.value] = updated;
return updated; return updated;
} }
@ -1128,12 +1198,12 @@ DefId DataFlowGraphBuilder::visitLValue(AstExprIndexExpr* i, DefId incomingDef)
DefId parentDef = visitExpr(i->expr).def; DefId parentDef = visitExpr(i->expr).def;
visitExpr(i->index); visitExpr(i->index);
DfgScope* scope = currentScope(); DfgScope* scope = FFlag::LuauDfgScopeStackNotNull ? currentScope() : currentScope_DEPRECATED();
if (auto string = i->index->as<AstExprConstantString>()) if (auto string = i->index->as<AstExprConstantString>())
{ {
if (scope->canUpdateDefinition(parentDef, string->value.data)) if (scope->canUpdateDefinition(parentDef, string->value.data))
{ {
DefId updated = defArena->freshCell(containsSubscriptedDefinition(incomingDef)); DefId updated = defArena->freshCell(Symbol{}, i->location, containsSubscriptedDefinition(incomingDef));
scope->props[parentDef][string->value.data] = updated; scope->props[parentDef][string->value.data] = updated;
return updated; return updated;
} }
@ -1141,7 +1211,7 @@ DefId DataFlowGraphBuilder::visitLValue(AstExprIndexExpr* i, DefId incomingDef)
return visitExpr(static_cast<AstExpr*>(i)).def; return visitExpr(static_cast<AstExpr*>(i)).def;
} }
else else
return defArena->freshCell(/*subscripted=*/true); return defArena->freshCell(Symbol{}, i->location, /*subscripted=*/true);
} }
DefId DataFlowGraphBuilder::visitLValue(AstExprError* error, DefId incomingDef) DefId DataFlowGraphBuilder::visitLValue(AstExprError* error, DefId incomingDef)
@ -1159,6 +1229,8 @@ void DataFlowGraphBuilder::visitType(AstType* t)
return visitType(f); return visitType(f);
else if (auto tyof = t->as<AstTypeTypeof>()) else if (auto tyof = t->as<AstTypeTypeof>())
return visitType(tyof); return visitType(tyof);
else if (auto o = t->as<AstTypeOptional>())
return;
else if (auto u = t->as<AstTypeUnion>()) else if (auto u = t->as<AstTypeUnion>())
return visitType(u); return visitType(u);
else if (auto i = t->as<AstTypeIntersection>()) else if (auto i = t->as<AstTypeIntersection>())

View file

@ -36,9 +36,9 @@ void collectOperands(DefId def, std::vector<DefId>* operands)
} }
} }
DefId DefArena::freshCell(bool subscripted) DefId DefArena::freshCell(Symbol sym, Location location, bool subscripted)
{ {
return NotNull{allocator.allocate(Def{Cell{subscripted}})}; return NotNull{allocator.allocate(Def{Cell{subscripted}, sym, location})};
} }
DefId DefArena::phi(DefId a, DefId b) DefId DefArena::phi(DefId a, DefId b)

View file

@ -1,8 +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/BuiltinDefinitions.h" #include "Luau/BuiltinDefinitions.h"
LUAU_FASTFLAGVARIABLE(LuauDebugInfoDefn)
namespace Luau namespace Luau
{ {
@ -215,15 +213,6 @@ declare debug: {
)BUILTIN_SRC"; )BUILTIN_SRC";
static const std::string kBuiltinDefinitionDebugSrc_DEPRECATED = R"BUILTIN_SRC(
declare debug: {
info: (<R...>(thread: thread, level: number, options: string) -> R...) & (<R...>(level: number, options: string) -> R...) & (<A..., R1..., R2...>(func: (A...) -> R1..., options: string) -> R2...),
traceback: ((message: string?, level: number?) -> string) & ((thread: thread, message: string?, level: number?) -> string),
}
)BUILTIN_SRC";
static const std::string kBuiltinDefinitionUtf8Src = R"BUILTIN_SRC( static const std::string kBuiltinDefinitionUtf8Src = R"BUILTIN_SRC(
declare utf8: { declare utf8: {
@ -309,7 +298,7 @@ std::string getBuiltinDefinitionSource()
result += kBuiltinDefinitionOsSrc; result += kBuiltinDefinitionOsSrc;
result += kBuiltinDefinitionCoroutineSrc; result += kBuiltinDefinitionCoroutineSrc;
result += kBuiltinDefinitionTableSrc; result += kBuiltinDefinitionTableSrc;
result += FFlag::LuauDebugInfoDefn ? kBuiltinDefinitionDebugSrc : kBuiltinDefinitionDebugSrc_DEPRECATED; result += kBuiltinDefinitionDebugSrc;
result += kBuiltinDefinitionUtf8Src; result += kBuiltinDefinitionUtf8Src;
result += kBuiltinDefinitionBufferSrc; result += kBuiltinDefinitionBufferSrc;
result += kBuiltinDefinitionVectorSrc; result += kBuiltinDefinitionVectorSrc;
@ -317,4 +306,83 @@ std::string getBuiltinDefinitionSource()
return result; return result;
} }
// TODO: split into separate tagged unions when the new solver can appropriately handle that.
static const std::string kBuiltinDefinitionTypesSrc = R"BUILTIN_SRC(
export type type = {
tag: "nil" | "unknown" | "never" | "any" | "boolean" | "number" | "string" | "buffer" | "thread" |
"singleton" | "negation" | "union" | "intesection" | "table" | "function" | "class" | "generic",
is: (self: type, arg: string) -> boolean,
-- for singleton type
value: (self: type) -> (string | boolean | nil),
-- for negation type
inner: (self: type) -> type,
-- for union and intersection types
components: (self: type) -> {type},
-- for table type
setproperty: (self: type, key: type, value: type?) -> (),
setreadproperty: (self: type, key: type, value: type?) -> (),
setwriteproperty: (self: type, key: type, value: type?) -> (),
readproperty: (self: type, key: type) -> type?,
writeproperty: (self: type, key: type) -> type?,
properties: (self: type) -> { [type]: { read: type?, write: type? } },
setindexer: (self: type, index: type, result: type) -> (),
setreadindexer: (self: type, index: type, result: type) -> (),
setwriteindexer: (self: type, index: type, result: type) -> (),
indexer: (self: type) -> { index: type, readresult: type, writeresult: type }?,
readindexer: (self: type) -> { index: type, result: type }?,
writeindexer: (self: type) -> { index: type, result: type }?,
setmetatable: (self: type, arg: type) -> (),
metatable: (self: type) -> type?,
-- for function type
setparameters: (self: type, head: {type}?, tail: type?) -> (),
parameters: (self: type) -> { head: {type}?, tail: type? },
setreturns: (self: type, head: {type}?, tail: type? ) -> (),
returns: (self: type) -> { head: {type}?, tail: type? },
setgenerics: (self: type, {type}?) -> (),
generics: (self: type) -> {type},
-- for class type
-- 'properties', 'metatable', 'indexer', 'readindexer' and 'writeindexer' are shared with table type
readparent: (self: type) -> type?,
writeparent: (self: type) -> type?,
-- for generic type
name: (self: type) -> string?,
ispack: (self: type) -> boolean,
}
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,
generic: @checked (name: string, ispack: boolean?) -> type,
negationof: @checked (arg: type) -> type,
unionof: @checked (...type) -> type,
intersectionof: @checked (...type) -> type,
newtable: @checked (props: {[type]: type} | {[type]: { read: type, write: type } } | nil, indexer: { index: type, readresult: type, writeresult: type }?, metatable: type?) -> type,
newfunction: @checked (parameters: { head: {type}?, tail: type? }?, returns: { head: {type}?, tail: type? }?, generics: {type}?) -> type,
copy: @checked (arg: type) -> type,
}
)BUILTIN_SRC";
std::string getTypeFunctionDefinitionSource()
{
return kBuiltinDefinitionTypesSrc;
}
} // namespace Luau } // namespace Luau

View file

@ -8,6 +8,7 @@
#include "Luau/StringUtils.h" #include "Luau/StringUtils.h"
#include "Luau/ToString.h" #include "Luau/ToString.h"
#include "Luau/Type.h" #include "Luau/Type.h"
#include "Luau/TypeChecker2.h"
#include "Luau/TypeFunction.h" #include "Luau/TypeFunction.h"
#include <optional> #include <optional>
@ -17,6 +18,7 @@
#include <unordered_set> #include <unordered_set>
LUAU_FASTINTVARIABLE(LuauIndentTypeMismatchMaxTypeLength, 10) LUAU_FASTINTVARIABLE(LuauIndentTypeMismatchMaxTypeLength, 10)
LUAU_FASTFLAG(LuauNonStrictFuncDefErrorFix)
static std::string wrongNumberOfArgsString( static std::string wrongNumberOfArgsString(
size_t expectedCount, size_t expectedCount,
@ -116,7 +118,10 @@ struct ErrorConverter
size_t luauIndentTypeMismatchMaxTypeLength = size_t(FInt::LuauIndentTypeMismatchMaxTypeLength); size_t luauIndentTypeMismatchMaxTypeLength = size_t(FInt::LuauIndentTypeMismatchMaxTypeLength);
if (givenType.length() <= luauIndentTypeMismatchMaxTypeLength || wantedType.length() <= luauIndentTypeMismatchMaxTypeLength) if (givenType.length() <= luauIndentTypeMismatchMaxTypeLength || wantedType.length() <= luauIndentTypeMismatchMaxTypeLength)
return "Type " + given + " could not be converted into " + wanted; return "Type " + given + " could not be converted into " + wanted;
return "Type\n " + given + "\ncould not be converted into\n " + wanted; if (FFlag::LuauImproveTypePathsInErrors)
return "Type\n\t" + given + "\ncould not be converted into\n\t" + wanted;
else
return "Type\n " + given + "\ncould not be converted into\n " + wanted;
}; };
if (givenTypeName == wantedTypeName) if (givenTypeName == wantedTypeName)
@ -601,7 +606,7 @@ struct ErrorConverter
auto tfit = get<TypeFunctionInstanceType>(e.ty); auto tfit = get<TypeFunctionInstanceType>(e.ty);
LUAU_ASSERT(tfit); // Luau analysis has actually done something wrong if this type is not a type function. LUAU_ASSERT(tfit); // Luau analysis has actually done something wrong if this type is not a type function.
if (!tfit) if (!tfit)
return "Unexpected type " + Luau::toString(e.ty) + " flagged as an uninhabited type function."; return "Internal error: Unexpected type " + Luau::toString(e.ty) + " flagged as an uninhabited type function.";
// unary operators // unary operators
if (auto unaryString = kUnaryOps.find(tfit->function->name); unaryString != kUnaryOps.end()) if (auto unaryString = kUnaryOps.find(tfit->function->name); unaryString != kUnaryOps.end())
@ -751,8 +756,15 @@ struct ErrorConverter
std::string operator()(const NonStrictFunctionDefinitionError& e) const std::string operator()(const NonStrictFunctionDefinitionError& e) const
{ {
return "Argument " + e.argument + " with type '" + toString(e.argumentType) + "' in function '" + e.functionName + if (FFlag::LuauNonStrictFuncDefErrorFix && e.functionName.empty())
"' 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";
}
else
{
return "Argument " + e.argument + " with type '" + toString(e.argumentType) + "' in function '" + e.functionName +
"' is used in a way that will run time error";
}
} }
std::string operator()(const PropertyAccessViolation& e) const std::string operator()(const PropertyAccessViolation& e) const

View file

@ -0,0 +1,172 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/FileResolver.h"
#include "Luau/Common.h"
#include "Luau/StringUtils.h"
#include <algorithm>
#include <memory>
#include <optional>
#include <string_view>
#include <utility>
LUAU_FASTFLAGVARIABLE(LuauExposeRequireByStringAutocomplete)
LUAU_FASTFLAGVARIABLE(LuauEscapeCharactersInRequireSuggestions)
LUAU_FASTFLAGVARIABLE(LuauHideImpossibleRequireSuggestions)
namespace Luau
{
static std::optional<RequireSuggestions> processRequireSuggestions(std::optional<RequireSuggestions> suggestions)
{
if (!suggestions)
return suggestions;
if (FFlag::LuauEscapeCharactersInRequireSuggestions)
{
for (RequireSuggestion& suggestion : *suggestions)
{
suggestion.fullPath = escape(suggestion.fullPath);
}
}
return suggestions;
}
static RequireSuggestions makeSuggestionsFromAliases(std::vector<RequireAlias> aliases)
{
RequireSuggestions result;
for (RequireAlias& alias : aliases)
{
RequireSuggestion suggestion;
suggestion.label = "@" + std::move(alias.alias);
suggestion.fullPath = suggestion.label;
suggestion.tags = std::move(alias.tags);
result.push_back(std::move(suggestion));
}
return result;
}
static RequireSuggestions makeSuggestionsForFirstComponent(std::unique_ptr<RequireNode> node)
{
RequireSuggestions result = makeSuggestionsFromAliases(node->getAvailableAliases());
result.push_back(RequireSuggestion{"./", "./", {}});
result.push_back(RequireSuggestion{"../", "../", {}});
return result;
}
static RequireSuggestions makeSuggestionsFromNode(std::unique_ptr<RequireNode> node, const std::string_view path, bool isPartialPath)
{
LUAU_ASSERT(!path.empty());
RequireSuggestions result;
const size_t lastSlashInPath = path.find_last_of('/');
if (lastSlashInPath != std::string_view::npos)
{
// Add a suggestion for the parent directory
RequireSuggestion parentSuggestion;
parentSuggestion.label = "..";
// TODO: after exposing require-by-string's path normalization API, this
// if-else can be replaced. Instead, we can simply normalize the result
// of inserting ".." at the end of the current path.
if (lastSlashInPath >= 2 && path.substr(lastSlashInPath - 2, 3) == "../")
{
parentSuggestion.fullPath = path.substr(0, lastSlashInPath + 1);
parentSuggestion.fullPath += "..";
}
else
{
parentSuggestion.fullPath = path.substr(0, lastSlashInPath);
}
result.push_back(std::move(parentSuggestion));
}
std::string fullPathPrefix;
if (isPartialPath)
{
// ./path/to/chi -> ./path/to/
fullPathPrefix += path.substr(0, lastSlashInPath + 1);
}
else
{
if (path.back() == '/')
{
// ./path/to/ -> ./path/to/
fullPathPrefix += path;
}
else
{
// ./path/to -> ./path/to/
fullPathPrefix += path;
fullPathPrefix += "/";
}
}
for (const std::unique_ptr<RequireNode>& child : node->getChildren())
{
if (!child)
continue;
std::string pathComponent = child->getPathComponent();
if (FFlag::LuauHideImpossibleRequireSuggestions)
{
// If path component contains a slash, it cannot be required by string.
// There's no point suggesting it.
if (pathComponent.find('/') != std::string::npos)
continue;
}
RequireSuggestion suggestion;
suggestion.label = isPartialPath || path.back() == '/' ? child->getLabel() : "/" + child->getLabel();
suggestion.fullPath = fullPathPrefix + std::move(pathComponent);
suggestion.tags = child->getTags();
result.push_back(std::move(suggestion));
}
return result;
}
std::optional<RequireSuggestions> RequireSuggester::getRequireSuggestionsImpl(const ModuleName& requirer, const std::optional<std::string>& path)
const
{
if (!path)
return std::nullopt;
std::unique_ptr<RequireNode> requirerNode = getNode(requirer);
if (!requirerNode)
return std::nullopt;
const size_t slashPos = path->find_last_of('/');
if (slashPos == std::string::npos)
return makeSuggestionsForFirstComponent(std::move(requirerNode));
// If path already points at a Node, return the Node's children as paths.
if (std::unique_ptr<RequireNode> node = requirerNode->resolvePathToNode(*path))
return makeSuggestionsFromNode(std::move(node), *path, /* isPartialPath = */ false);
// Otherwise, recover a partial path and use this to generate suggestions.
if (std::unique_ptr<RequireNode> partialNode = requirerNode->resolvePathToNode(path->substr(0, slashPos)))
return makeSuggestionsFromNode(std::move(partialNode), *path, /* isPartialPath = */ true);
return std::nullopt;
}
std::optional<RequireSuggestions> RequireSuggester::getRequireSuggestions(const ModuleName& requirer, const std::optional<std::string>& path) const
{
return processRequireSuggestions(getRequireSuggestionsImpl(requirer, path));
}
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;
}
} // namespace Luau

File diff suppressed because it is too large Load diff

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/Frontend.h" #include "Luau/Frontend.h"
#include "Luau/AnyTypeSummary.h"
#include "Luau/BuiltinDefinitions.h" #include "Luau/BuiltinDefinitions.h"
#include "Luau/Clone.h" #include "Luau/Clone.h"
#include "Luau/Common.h" #include "Luau/Common.h"
@ -40,19 +39,15 @@ LUAU_FASTINT(LuauTarjanChildLimit)
LUAU_FASTFLAG(LuauInferInNoCheckMode) LUAU_FASTFLAG(LuauInferInNoCheckMode)
LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3) LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauRethrowKnownExceptions, false)
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_DYNAMIC_FASTFLAGVARIABLE(LuauRunCustomModuleChecks, false)
LUAU_FASTFLAGVARIABLE(LuauBetterReverseDependencyTracking)
LUAU_FASTFLAG(StudioReportLuauAny2)
LUAU_FASTFLAGVARIABLE(LuauStoreSolverTypeOnModule)
LUAU_FASTFLAGVARIABLE(LuauSelectivelyRetainDFGArena) LUAU_FASTFLAGVARIABLE(LuauSelectivelyRetainDFGArena)
LUAU_FASTFLAG(LuauTypeFunResultInAutocomplete)
namespace Luau namespace Luau
{ {
@ -82,6 +77,20 @@ struct BuildQueueItem
Frontend::Stats stats; Frontend::Stats stats;
}; };
struct BuildQueueWorkState
{
std::function<void(std::function<void()> task)> executeTask;
std::vector<BuildQueueItem> buildQueueItems;
std::mutex mtx;
std::condition_variable cv;
std::vector<size_t> readyQueueItems;
size_t processing = 0;
size_t remaining = 0;
};
std::optional<Mode> parseMode(const std::vector<HotComment>& hotcomments) std::optional<Mode> parseMode(const std::vector<HotComment>& hotcomments)
{ {
for (const HotComment& hc : hotcomments) for (const HotComment& hc : hotcomments)
@ -446,20 +455,6 @@ CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOption
if (item.name == name) if (item.name == name)
checkResult.lintResult = item.module->lintResult; checkResult.lintResult = item.module->lintResult;
if (FFlag::StudioReportLuauAny2 && item.options.retainFullTypeGraphs)
{
if (item.module)
{
const SourceModule& sourceModule = *item.sourceModule;
if (sourceModule.mode == Luau::Mode::Strict)
{
item.module->ats.root = toString(sourceModule.root);
}
item.module->ats.rootSrc = sourceModule.root;
item.module->ats.traverse(item.module.get(), sourceModule.root, NotNull{&builtinTypes_});
}
}
} }
return checkResult; return checkResult;
@ -490,7 +485,8 @@ std::vector<ModuleName> Frontend::checkQueuedModules(
std::swap(currModuleQueue, moduleQueue); std::swap(currModuleQueue, moduleQueue);
DenseHashSet<Luau::ModuleName> seen{{}}; DenseHashSet<Luau::ModuleName> seen{{}};
std::vector<BuildQueueItem> buildQueueItems;
std::shared_ptr<BuildQueueWorkState> state = std::make_shared<BuildQueueWorkState>();
for (const ModuleName& name : currModuleQueue) for (const ModuleName& name : currModuleQueue)
{ {
@ -514,18 +510,18 @@ std::vector<ModuleName> Frontend::checkQueuedModules(
} }
); );
addBuildQueueItems(buildQueueItems, queue, cycleDetected, seen, frontendOptions); addBuildQueueItems(state->buildQueueItems, queue, cycleDetected, seen, frontendOptions);
} }
if (buildQueueItems.empty()) if (state->buildQueueItems.empty())
return {}; return {};
// We need a mapping from modules to build queue slots // We need a mapping from modules to build queue slots
std::unordered_map<ModuleName, size_t> moduleNameToQueue; std::unordered_map<ModuleName, size_t> moduleNameToQueue;
for (size_t i = 0; i < buildQueueItems.size(); i++) for (size_t i = 0; i < state->buildQueueItems.size(); i++)
{ {
BuildQueueItem& item = buildQueueItems[i]; BuildQueueItem& item = state->buildQueueItems[i];
moduleNameToQueue[item.name] = i; moduleNameToQueue[item.name] = i;
} }
@ -538,67 +534,13 @@ std::vector<ModuleName> Frontend::checkQueuedModules(
}; };
} }
std::mutex mtx; state->executeTask = executeTask;
std::condition_variable cv; state->remaining = state->buildQueueItems.size();
std::vector<size_t> readyQueueItems;
size_t processing = 0; // Record dependencies between modules
size_t remaining = buildQueueItems.size(); for (size_t i = 0; i < state->buildQueueItems.size(); i++)
auto itemTask = [&](size_t i)
{ {
BuildQueueItem& item = buildQueueItems[i]; BuildQueueItem& item = state->buildQueueItems[i];
try
{
checkBuildQueueItem(item);
}
catch (...)
{
item.exception = std::current_exception();
}
{
std::unique_lock guard(mtx);
readyQueueItems.push_back(i);
}
cv.notify_one();
};
auto sendItemTask = [&](size_t i)
{
BuildQueueItem& item = buildQueueItems[i];
item.processing = true;
processing++;
executeTask(
[&itemTask, i]()
{
itemTask(i);
}
);
};
auto sendCycleItemTask = [&]
{
for (size_t i = 0; i < buildQueueItems.size(); i++)
{
BuildQueueItem& item = buildQueueItems[i];
if (!item.processing)
{
sendItemTask(i);
break;
}
}
};
// In a first pass, check modules that have no dependencies and record info of those modules that wait
for (size_t i = 0; i < buildQueueItems.size(); i++)
{
BuildQueueItem& item = buildQueueItems[i];
for (const ModuleName& dep : item.sourceNode->requireSet) for (const ModuleName& dep : item.sourceNode->requireSet)
{ {
@ -608,41 +550,45 @@ std::vector<ModuleName> Frontend::checkQueuedModules(
{ {
item.dirtyDependencies++; item.dirtyDependencies++;
buildQueueItems[moduleNameToQueue[dep]].reverseDeps.push_back(i); state->buildQueueItems[moduleNameToQueue[dep]].reverseDeps.push_back(i);
} }
} }
} }
if (item.dirtyDependencies == 0)
sendItemTask(i);
} }
// Not a single item was found, a cycle in the graph was hit // In the first pass, check all modules with no pending dependencies
if (processing == 0) for (size_t i = 0; i < state->buildQueueItems.size(); i++)
sendCycleItemTask(); {
if (state->buildQueueItems[i].dirtyDependencies == 0)
sendQueueItemTask(state, i);
}
// If not a single item was found, a cycle in the graph was hit
if (state->processing == 0)
sendQueueCycleItemTask(state);
std::vector<size_t> nextItems; std::vector<size_t> nextItems;
std::optional<size_t> itemWithException; std::optional<size_t> itemWithException;
bool cancelled = false; bool cancelled = false;
while (remaining != 0) while (state->remaining != 0)
{ {
{ {
std::unique_lock guard(mtx); std::unique_lock guard(state->mtx);
// If nothing is ready yet, wait // If nothing is ready yet, wait
cv.wait( state->cv.wait(
guard, guard,
[&readyQueueItems] [state]
{ {
return !readyQueueItems.empty(); return !state->readyQueueItems.empty();
} }
); );
// Handle checked items // Handle checked items
for (size_t i : readyQueueItems) for (size_t i : state->readyQueueItems)
{ {
const BuildQueueItem& item = buildQueueItems[i]; const BuildQueueItem& item = state->buildQueueItems[i];
// If exception was thrown, stop adding new items and wait for processing items to complete // If exception was thrown, stop adding new items and wait for processing items to complete
if (item.exception) if (item.exception)
@ -659,7 +605,7 @@ std::vector<ModuleName> Frontend::checkQueuedModules(
// Notify items that were waiting for this dependency // Notify items that were waiting for this dependency
for (size_t reverseDep : item.reverseDeps) for (size_t reverseDep : item.reverseDeps)
{ {
BuildQueueItem& reverseDepItem = buildQueueItems[reverseDep]; BuildQueueItem& reverseDepItem = state->buildQueueItems[reverseDep];
LUAU_ASSERT(reverseDepItem.dirtyDependencies != 0); LUAU_ASSERT(reverseDepItem.dirtyDependencies != 0);
reverseDepItem.dirtyDependencies--; reverseDepItem.dirtyDependencies--;
@ -670,26 +616,26 @@ std::vector<ModuleName> Frontend::checkQueuedModules(
} }
} }
LUAU_ASSERT(processing >= readyQueueItems.size()); LUAU_ASSERT(state->processing >= state->readyQueueItems.size());
processing -= readyQueueItems.size(); state->processing -= state->readyQueueItems.size();
LUAU_ASSERT(remaining >= readyQueueItems.size()); LUAU_ASSERT(state->remaining >= state->readyQueueItems.size());
remaining -= readyQueueItems.size(); state->remaining -= state->readyQueueItems.size();
readyQueueItems.clear(); state->readyQueueItems.clear();
} }
if (progress) if (progress)
{ {
if (!progress(buildQueueItems.size() - remaining, buildQueueItems.size())) if (!progress(state->buildQueueItems.size() - state->remaining, state->buildQueueItems.size()))
cancelled = true; cancelled = true;
} }
// Items cannot be submitted while holding the lock // Items cannot be submitted while holding the lock
for (size_t i : nextItems) for (size_t i : nextItems)
sendItemTask(i); sendQueueItemTask(state, i);
nextItems.clear(); nextItems.clear();
if (processing == 0) if (state->processing == 0)
{ {
// Typechecking might have been cancelled by user, don't return partial results // Typechecking might have been cancelled by user, don't return partial results
if (cancelled) if (cancelled)
@ -697,19 +643,19 @@ std::vector<ModuleName> Frontend::checkQueuedModules(
// We might have stopped because of a pending exception // We might have stopped because of a pending exception
if (itemWithException) if (itemWithException)
recordItemResult(buildQueueItems[*itemWithException]); recordItemResult(state->buildQueueItems[*itemWithException]);
} }
// If we aren't done, but don't have anything processing, we hit a cycle // If we aren't done, but don't have anything processing, we hit a cycle
if (remaining != 0 && processing == 0) if (state->remaining != 0 && state->processing == 0)
sendCycleItemTask(); sendQueueCycleItemTask(state);
} }
std::vector<ModuleName> checkedModules; std::vector<ModuleName> checkedModules;
checkedModules.reserve(buildQueueItems.size()); checkedModules.reserve(state->buildQueueItems.size());
for (size_t i = 0; i < buildQueueItems.size(); i++) for (size_t i = 0; i < state->buildQueueItems.size(); i++)
checkedModules.push_back(std::move(buildQueueItems[i].name)); checkedModules.push_back(std::move(state->buildQueueItems[i].name));
return checkedModules; return checkedModules;
} }
@ -822,14 +768,11 @@ bool Frontend::parseGraph(
buildQueue.push_back(top->name); buildQueue.push_back(top->name);
if (FFlag::LuauBetterReverseDependencyTracking) // at this point we know all valid dependencies are processed into SourceNodes
for (const ModuleName& dep : top->requireSet)
{ {
// at this point we know all valid dependencies are processed into SourceNodes if (auto it = sourceNodes.find(dep); it != sourceNodes.end())
for (const ModuleName& dep : top->requireSet) it->second->dependents.insert(top->name);
{
if (auto it = sourceNodes.find(dep); it != sourceNodes.end())
it->second->dependents.insert(top->name);
}
} }
} }
else else
@ -1008,7 +951,7 @@ void Frontend::checkBuildQueueItem(BuildQueueItem& item)
item.stats.timeCheck += duration; item.stats.timeCheck += duration;
item.stats.filesStrict += 1; item.stats.filesStrict += 1;
if (DFFlag::LuauRunCustomModuleChecks && item.options.customModuleCheck) if (item.options.customModuleCheck)
item.options.customModuleCheck(sourceModule, *moduleForAutocomplete); item.options.customModuleCheck(sourceModule, *moduleForAutocomplete);
item.module = moduleForAutocomplete; item.module = moduleForAutocomplete;
@ -1028,7 +971,7 @@ void Frontend::checkBuildQueueItem(BuildQueueItem& item)
item.stats.filesStrict += mode == Mode::Strict; item.stats.filesStrict += mode == Mode::Strict;
item.stats.filesNonstrict += mode == Mode::Nonstrict; item.stats.filesNonstrict += mode == Mode::Nonstrict;
if (DFFlag::LuauRunCustomModuleChecks && item.options.customModuleCheck) if (item.options.customModuleCheck)
item.options.customModuleCheck(sourceModule, *module); item.options.customModuleCheck(sourceModule, *module);
if (FFlag::LuauSolverV2 && mode == Mode::NoCheck) if (FFlag::LuauSolverV2 && mode == Mode::NoCheck)
@ -1118,51 +1061,35 @@ void Frontend::recordItemResult(const BuildQueueItem& item)
if (item.exception) if (item.exception)
std::rethrow_exception(item.exception); std::rethrow_exception(item.exception);
if (FFlag::LuauBetterReverseDependencyTracking) bool replacedModule = false;
if (item.options.forAutocomplete)
{ {
bool replacedModule = false; replacedModule = moduleResolverForAutocomplete.setModule(item.name, item.module);
if (item.options.forAutocomplete) item.sourceNode->dirtyModuleForAutocomplete = false;
{
replacedModule = moduleResolverForAutocomplete.setModule(item.name, item.module);
item.sourceNode->dirtyModuleForAutocomplete = false;
}
else
{
replacedModule = moduleResolver.setModule(item.name, item.module);
item.sourceNode->dirtyModule = false;
}
if (replacedModule)
{
LUAU_TIMETRACE_SCOPE("Frontend::invalidateDependentModules", "Frontend");
LUAU_TIMETRACE_ARGUMENT("name", item.name.c_str());
traverseDependents(
item.name,
[forAutocomplete = item.options.forAutocomplete](SourceNode& sourceNode)
{
bool traverseSubtree = !sourceNode.hasInvalidModuleDependency(forAutocomplete);
sourceNode.setInvalidModuleDependency(true, forAutocomplete);
return traverseSubtree;
}
);
}
item.sourceNode->setInvalidModuleDependency(false, item.options.forAutocomplete);
} }
else else
{ {
if (item.options.forAutocomplete) replacedModule = moduleResolver.setModule(item.name, item.module);
{ item.sourceNode->dirtyModule = false;
moduleResolverForAutocomplete.setModule(item.name, item.module);
item.sourceNode->dirtyModuleForAutocomplete = false;
}
else
{
moduleResolver.setModule(item.name, item.module);
item.sourceNode->dirtyModule = false;
}
} }
if (replacedModule)
{
LUAU_TIMETRACE_SCOPE("Frontend::invalidateDependentModules", "Frontend");
LUAU_TIMETRACE_ARGUMENT("name", item.name.c_str());
traverseDependents(
item.name,
[forAutocomplete = item.options.forAutocomplete](SourceNode& sourceNode)
{
bool traverseSubtree = !sourceNode.hasInvalidModuleDependency(forAutocomplete);
sourceNode.setInvalidModuleDependency(true, forAutocomplete);
return traverseSubtree;
}
);
}
item.sourceNode->setInvalidModuleDependency(false, item.options.forAutocomplete);
stats.timeCheck += item.stats.timeCheck; stats.timeCheck += item.stats.timeCheck;
stats.timeLint += item.stats.timeLint; stats.timeLint += item.stats.timeLint;
@ -1170,6 +1097,72 @@ void Frontend::recordItemResult(const BuildQueueItem& item)
stats.filesNonstrict += item.stats.filesNonstrict; stats.filesNonstrict += item.stats.filesNonstrict;
} }
void Frontend::performQueueItemTask(std::shared_ptr<BuildQueueWorkState> state, size_t itemPos)
{
BuildQueueItem& item = state->buildQueueItems[itemPos];
if (DFFlag::LuauRethrowKnownExceptions)
{
try
{
checkBuildQueueItem(item);
}
catch (const Luau::InternalCompilerError&)
{
item.exception = std::current_exception();
}
}
else
{
try
{
checkBuildQueueItem(item);
}
catch (...)
{
item.exception = std::current_exception();
}
}
{
std::unique_lock guard(state->mtx);
state->readyQueueItems.push_back(itemPos);
}
state->cv.notify_one();
}
void Frontend::sendQueueItemTask(std::shared_ptr<BuildQueueWorkState> state, size_t itemPos)
{
BuildQueueItem& item = state->buildQueueItems[itemPos];
LUAU_ASSERT(!item.processing);
item.processing = true;
state->processing++;
state->executeTask(
[this, state, itemPos]()
{
performQueueItemTask(state, itemPos);
}
);
}
void Frontend::sendQueueCycleItemTask(std::shared_ptr<BuildQueueWorkState> state)
{
for (size_t i = 0; i < state->buildQueueItems.size(); i++)
{
BuildQueueItem& item = state->buildQueueItems[i];
if (!item.processing)
{
sendQueueItemTask(state, i);
break;
}
}
}
ScopePtr Frontend::getModuleEnvironment(const SourceModule& module, const Config& config, bool forAutocomplete) const ScopePtr Frontend::getModuleEnvironment(const SourceModule& module, const Config& config, bool forAutocomplete) const
{ {
ScopePtr result; ScopePtr result;
@ -1199,7 +1192,6 @@ ScopePtr Frontend::getModuleEnvironment(const SourceModule& module, const Config
bool Frontend::allModuleDependenciesValid(const ModuleName& name, bool forAutocomplete) const bool Frontend::allModuleDependenciesValid(const ModuleName& name, bool forAutocomplete) const
{ {
LUAU_ASSERT(FFlag::LuauBetterReverseDependencyTracking);
auto it = sourceNodes.find(name); auto it = sourceNodes.find(name);
return it != sourceNodes.end() && !it->second->hasInvalidModuleDependency(forAutocomplete); return it != sourceNodes.end() && !it->second->hasInvalidModuleDependency(forAutocomplete);
} }
@ -1221,72 +1213,27 @@ void Frontend::markDirty(const ModuleName& name, std::vector<ModuleName>* marked
LUAU_TIMETRACE_SCOPE("Frontend::markDirty", "Frontend"); LUAU_TIMETRACE_SCOPE("Frontend::markDirty", "Frontend");
LUAU_TIMETRACE_ARGUMENT("name", name.c_str()); LUAU_TIMETRACE_ARGUMENT("name", name.c_str());
if (FFlag::LuauBetterReverseDependencyTracking) traverseDependents(
{ name,
traverseDependents( [markedDirty](SourceNode& sourceNode)
name,
[markedDirty](SourceNode& sourceNode)
{
if (markedDirty)
markedDirty->push_back(sourceNode.name);
if (sourceNode.dirtySourceModule && sourceNode.dirtyModule && sourceNode.dirtyModuleForAutocomplete)
return false;
sourceNode.dirtySourceModule = true;
sourceNode.dirtyModule = true;
sourceNode.dirtyModuleForAutocomplete = true;
return true;
}
);
}
else
{
if (sourceNodes.count(name) == 0)
return;
std::unordered_map<ModuleName, std::vector<ModuleName>> reverseDeps;
for (const auto& module : sourceNodes)
{ {
for (const auto& dep : module.second->requireSet)
reverseDeps[dep].push_back(module.first);
}
std::vector<ModuleName> queue{name};
while (!queue.empty())
{
ModuleName next = std::move(queue.back());
queue.pop_back();
LUAU_ASSERT(sourceNodes.count(next) > 0);
SourceNode& sourceNode = *sourceNodes[next];
if (markedDirty) if (markedDirty)
markedDirty->push_back(next); markedDirty->push_back(sourceNode.name);
if (sourceNode.dirtySourceModule && sourceNode.dirtyModule && sourceNode.dirtyModuleForAutocomplete) if (sourceNode.dirtySourceModule && sourceNode.dirtyModule && sourceNode.dirtyModuleForAutocomplete)
continue; return false;
sourceNode.dirtySourceModule = true; sourceNode.dirtySourceModule = true;
sourceNode.dirtyModule = true; sourceNode.dirtyModule = true;
sourceNode.dirtyModuleForAutocomplete = true; sourceNode.dirtyModuleForAutocomplete = true;
if (0 == reverseDeps.count(next)) return true;
continue;
sourceModules.erase(next);
const std::vector<ModuleName>& dependents = reverseDeps[next];
queue.insert(queue.end(), dependents.begin(), dependents.end());
} }
} );
} }
void Frontend::traverseDependents(const ModuleName& name, std::function<bool(SourceNode&)> processSubtree) void Frontend::traverseDependents(const ModuleName& name, std::function<bool(SourceNode&)> processSubtree)
{ {
LUAU_ASSERT(FFlag::LuauBetterReverseDependencyTracking);
LUAU_TIMETRACE_SCOPE("Frontend::traverseDependents", "Frontend"); LUAU_TIMETRACE_SCOPE("Frontend::traverseDependents", "Frontend");
if (sourceNodes.count(name) == 0) if (sourceNodes.count(name) == 0)
@ -1333,6 +1280,7 @@ ModulePtr check(
NotNull<ModuleResolver> moduleResolver, NotNull<ModuleResolver> moduleResolver,
NotNull<FileResolver> fileResolver, NotNull<FileResolver> fileResolver,
const ScopePtr& parentScope, const ScopePtr& parentScope,
const ScopePtr& typeFunctionScope,
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope, std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope,
FrontendOptions options, FrontendOptions options,
TypeCheckLimits limits, TypeCheckLimits limits,
@ -1349,6 +1297,7 @@ ModulePtr check(
moduleResolver, moduleResolver,
fileResolver, fileResolver,
parentScope, parentScope,
typeFunctionScope,
std::move(prepareModuleScope), std::move(prepareModuleScope),
options, options,
limits, limits,
@ -1410,6 +1359,7 @@ ModulePtr check(
NotNull<ModuleResolver> moduleResolver, NotNull<ModuleResolver> moduleResolver,
NotNull<FileResolver> fileResolver, NotNull<FileResolver> fileResolver,
const ScopePtr& parentScope, const ScopePtr& parentScope,
const ScopePtr& typeFunctionScope,
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope, std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope,
FrontendOptions options, FrontendOptions options,
TypeCheckLimits limits, TypeCheckLimits limits,
@ -1422,8 +1372,7 @@ ModulePtr check(
LUAU_TIMETRACE_ARGUMENT("name", sourceModule.humanReadableName.c_str()); LUAU_TIMETRACE_ARGUMENT("name", sourceModule.humanReadableName.c_str());
ModulePtr result = std::make_shared<Module>(); ModulePtr result = std::make_shared<Module>();
if (FFlag::LuauStoreSolverTypeOnModule) result->checkedInNewSolver = true;
result->checkedInNewSolver = true;
result->name = sourceModule.name; result->name = sourceModule.name;
result->humanReadableName = sourceModule.humanReadableName; result->humanReadableName = sourceModule.humanReadableName;
result->mode = mode; result->mode = mode;
@ -1431,6 +1380,7 @@ ModulePtr check(
result->interfaceTypes.owningModule = result.get(); result->interfaceTypes.owningModule = result.get();
result->allocator = sourceModule.allocator; result->allocator = sourceModule.allocator;
result->names = sourceModule.names; result->names = sourceModule.names;
result->root = sourceModule.root;
iceHandler->moduleName = sourceModule.name; iceHandler->moduleName = sourceModule.name;
@ -1455,7 +1405,7 @@ ModulePtr check(
SimplifierPtr simplifier = newSimplifier(NotNull{&result->internalTypes}, builtinTypes); SimplifierPtr simplifier = newSimplifier(NotNull{&result->internalTypes}, builtinTypes);
TypeFunctionRuntime typeFunctionRuntime{iceHandler, NotNull{&limits}}; TypeFunctionRuntime typeFunctionRuntime{iceHandler, NotNull{&limits}};
typeFunctionRuntime.allowEvaluation = sourceModule.parseErrors.empty(); typeFunctionRuntime.allowEvaluation = FFlag::LuauTypeFunResultInAutocomplete || sourceModule.parseErrors.empty();
ConstraintGenerator cg{ ConstraintGenerator cg{
result, result,
@ -1466,6 +1416,7 @@ ModulePtr check(
builtinTypes, builtinTypes,
iceHandler, iceHandler,
parentScope, parentScope,
typeFunctionScope,
std::move(prepareModuleScope), std::move(prepareModuleScope),
logger.get(), logger.get(),
NotNull{&dfg}, NotNull{&dfg},
@ -1648,6 +1599,7 @@ ModulePtr Frontend::check(
NotNull{forAutocomplete ? &moduleResolverForAutocomplete : &moduleResolver}, NotNull{forAutocomplete ? &moduleResolverForAutocomplete : &moduleResolver},
NotNull{fileResolver}, NotNull{fileResolver},
environmentScope ? *environmentScope : globals.globalScope, environmentScope ? *environmentScope : globals.globalScope,
globals.globalTypeFunctionScope,
prepareModuleScopeWrap, prepareModuleScopeWrap,
options, options,
typeCheckLimits, typeCheckLimits,
@ -1746,14 +1698,11 @@ std::pair<SourceNode*, SourceModule*> Frontend::getSourceNode(const ModuleName&
sourceNode->name = sourceModule->name; sourceNode->name = sourceModule->name;
sourceNode->humanReadableName = sourceModule->humanReadableName; sourceNode->humanReadableName = sourceModule->humanReadableName;
if (FFlag::LuauBetterReverseDependencyTracking) // clear all prior dependents. we will re-add them after parsing the rest of the graph
for (const auto& [moduleName, _] : sourceNode->requireLocations)
{ {
// clear all prior dependents. we will re-add them after parsing the rest of the graph if (auto depIt = sourceNodes.find(moduleName); depIt != sourceNodes.end())
for (const auto& [moduleName, _] : sourceNode->requireLocations) depIt->second->dependents.erase(sourceNode->name);
{
if (auto depIt = sourceNodes.find(moduleName); depIt != sourceNodes.end())
depIt->second->dependents.erase(sourceNode->name);
}
} }
sourceNode->requireSet.clear(); sourceNode->requireSet.clear();
@ -1881,17 +1830,9 @@ bool FrontendModuleResolver::setModule(const ModuleName& moduleName, ModulePtr m
{ {
std::scoped_lock lock(moduleMutex); std::scoped_lock lock(moduleMutex);
if (FFlag::LuauBetterReverseDependencyTracking) bool replaced = modules.count(moduleName) > 0;
{ modules[moduleName] = std::move(module);
bool replaced = modules.count(moduleName) > 0; return replaced;
modules[moduleName] = std::move(module);
return replaced;
}
else
{
modules[moduleName] = std::move(module);
return false;
}
} }
void FrontendModuleResolver::clearModules() void FrontendModuleResolver::clearModules()

View file

@ -4,19 +4,84 @@
#include "Luau/Common.h" #include "Luau/Common.h"
#include "Luau/DenseHash.h" #include "Luau/DenseHash.h"
#include "Luau/InsertionOrderedMap.h"
#include "Luau/Polarity.h"
#include "Luau/Scope.h" #include "Luau/Scope.h"
#include "Luau/Type.h"
#include "Luau/ToString.h" #include "Luau/ToString.h"
#include "Luau/Type.h"
#include "Luau/TypeArena.h" #include "Luau/TypeArena.h"
#include "Luau/TypePack.h" #include "Luau/TypePack.h"
#include "Luau/VisitType.h" #include "Luau/VisitType.h"
LUAU_FASTFLAG(LuauAutocompleteRefactorsForIncrementalAutocomplete) LUAU_FASTFLAG(LuauAutocompleteRefactorsForIncrementalAutocomplete)
LUAU_FASTFLAGVARIABLE(LuauGeneralizationRemoveRecursiveUpperBound2)
LUAU_FASTFLAGVARIABLE(LuauNonReentrantGeneralization)
namespace Luau namespace Luau
{ {
namespace
{
template<typename T>
struct OrderedSet
{
using iterator = typename std::vector<T>::iterator;
using const_iterator = typename std::vector<T>::const_iterator;
bool empty() const
{
return elements.empty();
}
size_t size() const
{
return elements.size();
}
void insert(T t)
{
if (!elementSet.contains(t))
{
elementSet.insert(t);
elements.push_back(t);
}
}
iterator begin()
{
return elements.begin();
}
const_iterator begin() const
{
return elements.begin();
}
iterator end()
{
return elements.end();
}
const_iterator end() const
{
return elements.end();
}
/// Move the underlying vector out of the OrderedSet.
std::vector<T> takeVector()
{
elementSet.clear();
return std::move(elements);
}
private:
std::vector<T> elements;
DenseHashSet<T> elementSet{nullptr};
};
} // namespace
struct MutatingGeneralizer : TypeOnceVisitor struct MutatingGeneralizer : TypeOnceVisitor
{ {
NotNull<TypeArena> arena; NotNull<TypeArena> arena;
@ -30,7 +95,6 @@ struct MutatingGeneralizer : TypeOnceVisitor
std::vector<TypePackId> genericPacks; std::vector<TypePackId> genericPacks;
bool isWithinFunction = false; bool isWithinFunction = false;
bool avoidSealingTables = false;
MutatingGeneralizer( MutatingGeneralizer(
NotNull<TypeArena> arena, NotNull<TypeArena> arena,
@ -38,8 +102,7 @@ struct MutatingGeneralizer : TypeOnceVisitor
NotNull<Scope> scope, NotNull<Scope> scope,
NotNull<DenseHashSet<TypeId>> cachedTypes, NotNull<DenseHashSet<TypeId>> cachedTypes,
DenseHashMap<const void*, size_t> positiveTypes, DenseHashMap<const void*, size_t> positiveTypes,
DenseHashMap<const void*, size_t> negativeTypes, DenseHashMap<const void*, size_t> negativeTypes
bool avoidSealingTables
) )
: TypeOnceVisitor(/* skipBoundTypes */ true) : TypeOnceVisitor(/* skipBoundTypes */ true)
, arena(arena) , arena(arena)
@ -48,7 +111,6 @@ struct MutatingGeneralizer : TypeOnceVisitor
, cachedTypes(cachedTypes) , cachedTypes(cachedTypes)
, positiveTypes(std::move(positiveTypes)) , positiveTypes(std::move(positiveTypes))
, negativeTypes(std::move(negativeTypes)) , negativeTypes(std::move(negativeTypes))
, avoidSealingTables(avoidSealingTables)
{ {
} }
@ -99,7 +161,7 @@ struct MutatingGeneralizer : TypeOnceVisitor
LUAU_ASSERT(onlyType != haystack); LUAU_ASSERT(onlyType != haystack);
emplaceType<BoundType>(asMutable(haystack), onlyType); emplaceType<BoundType>(asMutable(haystack), onlyType);
} }
else if (FFlag::LuauGeneralizationRemoveRecursiveUpperBound2 && ut->options.empty()) else if (ut->options.empty())
{ {
emplaceType<BoundType>(asMutable(haystack), builtinTypes->neverType); emplaceType<BoundType>(asMutable(haystack), builtinTypes->neverType);
} }
@ -146,7 +208,7 @@ struct MutatingGeneralizer : TypeOnceVisitor
LUAU_ASSERT(onlyType != needle); LUAU_ASSERT(onlyType != needle);
emplaceType<BoundType>(asMutable(needle), onlyType); emplaceType<BoundType>(asMutable(needle), onlyType);
} }
else if (FFlag::LuauGeneralizationRemoveRecursiveUpperBound2 && it->parts.empty()) else if (it->parts.empty())
{ {
emplaceType<BoundType>(asMutable(needle), builtinTypes->unknownType); emplaceType<BoundType>(asMutable(needle), builtinTypes->unknownType);
} }
@ -274,6 +336,15 @@ struct MutatingGeneralizer : TypeOnceVisitor
return 0; return 0;
} }
template<typename TID>
static size_t getCount(const DenseHashMap<TID, size_t>& map, TID ty)
{
if (const size_t* count = map.find(ty))
return *count;
else
return 0;
}
bool visit(TypeId ty, const TableType&) override bool visit(TypeId ty, const TableType&) override
{ {
if (cachedTypes->contains(ty)) if (cachedTypes->contains(ty))
@ -292,8 +363,7 @@ struct MutatingGeneralizer : TypeOnceVisitor
TableType* tt = getMutable<TableType>(ty); TableType* tt = getMutable<TableType>(ty);
LUAU_ASSERT(tt); LUAU_ASSERT(tt);
if (!avoidSealingTables) tt->state = TableState::Sealed;
tt->state = TableState::Sealed;
return true; return true;
} }
@ -332,38 +402,22 @@ struct FreeTypeSearcher : TypeVisitor
{ {
} }
enum Polarity bool isWithinFunction = false;
{ Polarity polarity = Polarity::Positive;
Positive,
Negative,
Both,
};
Polarity polarity = Positive;
void flip() void flip()
{ {
switch (polarity) polarity = invert(polarity);
{
case Positive:
polarity = Negative;
break;
case Negative:
polarity = Positive;
break;
case Both:
break;
}
} }
DenseHashSet<const void*> seenPositive{nullptr}; DenseHashSet<const void*> seenPositive{nullptr};
DenseHashSet<const void*> seenNegative{nullptr}; DenseHashSet<const void*> seenNegative{nullptr};
bool seenWithPolarity(const void* ty) bool seenWithCurrentPolarity(const void* ty)
{ {
switch (polarity) switch (polarity)
{ {
case Positive: case Polarity::Positive:
{ {
if (seenPositive.contains(ty)) if (seenPositive.contains(ty))
return true; return true;
@ -371,7 +425,7 @@ struct FreeTypeSearcher : TypeVisitor
seenPositive.insert(ty); seenPositive.insert(ty);
return false; return false;
} }
case Negative: case Polarity::Negative:
{ {
if (seenNegative.contains(ty)) if (seenNegative.contains(ty))
return true; return true;
@ -379,7 +433,7 @@ struct FreeTypeSearcher : TypeVisitor
seenNegative.insert(ty); seenNegative.insert(ty);
return false; return false;
} }
case Both: case Polarity::Mixed:
{ {
if (seenPositive.contains(ty) && seenNegative.contains(ty)) if (seenPositive.contains(ty) && seenNegative.contains(ty))
return true; return true;
@ -388,20 +442,24 @@ struct FreeTypeSearcher : TypeVisitor
seenNegative.insert(ty); seenNegative.insert(ty);
return false; return false;
} }
default:
LUAU_ASSERT(!"Unreachable");
} }
return false; 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> negativeTypes{0};
DenseHashMap<const void*, size_t> positiveTypes{0}; DenseHashMap<const void*, size_t> positiveTypes{0};
InsertionOrderedMap<TypeId, GeneralizationParams<TypeId>> types;
InsertionOrderedMap<TypePackId, GeneralizationParams<TypePackId>> typePacks;
OrderedSet<TypeId> unsealedTables;
bool visit(TypeId ty) override bool visit(TypeId ty) override
{ {
if (cachedTypes->contains(ty) || seenWithPolarity(ty)) if (cachedTypes->contains(ty) || seenWithCurrentPolarity(ty))
return false; return false;
LUAU_ASSERT(ty); LUAU_ASSERT(ty);
@ -410,24 +468,45 @@ struct FreeTypeSearcher : TypeVisitor
bool visit(TypeId ty, const FreeType& ft) override bool visit(TypeId ty, const FreeType& ft) override
{ {
if (cachedTypes->contains(ty) || seenWithPolarity(ty)) if (FFlag::LuauNonReentrantGeneralization)
return false;
if (!subsumes(scope, ft.scope))
return true;
switch (polarity)
{ {
case Positive: if (!subsumes(scope, ft.scope))
positiveTypes[ty]++; return true;
break;
case Negative: GeneralizationParams<TypeId>& params = types[ty];
negativeTypes[ty]++; ++params.useCount;
break;
case Both: if (cachedTypes->contains(ty) || seenWithCurrentPolarity(ty))
positiveTypes[ty]++; return false;
negativeTypes[ty]++;
break; if (!isWithinFunction)
params.foundOutsideFunctions = true;
params.polarity |= polarity;
}
else
{
if (cachedTypes->contains(ty) || 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; return true;
@ -435,23 +514,30 @@ struct FreeTypeSearcher : TypeVisitor
bool visit(TypeId ty, const TableType& tt) override bool visit(TypeId ty, const TableType& tt) override
{ {
if (cachedTypes->contains(ty) || seenWithPolarity(ty)) if (cachedTypes->contains(ty) || seenWithCurrentPolarity(ty))
return false; return false;
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))
{ {
switch (polarity) if (FFlag::LuauNonReentrantGeneralization)
unsealedTables.insert(ty);
else
{ {
case Positive: switch (polarity)
positiveTypes[ty]++; {
break; case Polarity::Positive:
case Negative: positiveTypes[ty]++;
negativeTypes[ty]++; break;
break; case Polarity::Negative:
case Both: negativeTypes[ty]++;
positiveTypes[ty]++; break;
negativeTypes[ty]++; case Polarity::Mixed:
break; positiveTypes[ty]++;
negativeTypes[ty]++;
break;
default:
LUAU_ASSERT(!"Unreachable");
}
} }
} }
@ -464,7 +550,7 @@ struct FreeTypeSearcher : TypeVisitor
LUAU_ASSERT(prop.isShared() || FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete); LUAU_ASSERT(prop.isShared() || FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete);
Polarity p = polarity; Polarity p = polarity;
polarity = Both; polarity = Polarity::Mixed;
traverse(prop.type()); traverse(prop.type());
polarity = p; polarity = p;
} }
@ -472,8 +558,27 @@ struct FreeTypeSearcher : TypeVisitor
if (tt.indexer) if (tt.indexer)
{ {
traverse(tt.indexer->indexType); if (FFlag::LuauNonReentrantGeneralization)
traverse(tt.indexer->indexResultType); {
// {[K]: V} is equivalent to three functions: get, set, and iterate
//
// (K) -> V
// (K, V) -> ()
// () -> {K}
//
// K and V therefore both have mixed polarity.
const Polarity p = polarity;
polarity = Polarity::Mixed;
traverse(tt.indexer->indexType);
traverse(tt.indexer->indexResultType);
polarity = p;
}
else
{
traverse(tt.indexer->indexType);
traverse(tt.indexer->indexResultType);
}
} }
return false; return false;
@ -481,15 +586,20 @@ struct FreeTypeSearcher : TypeVisitor
bool visit(TypeId ty, const FunctionType& ft) override bool visit(TypeId ty, const FunctionType& ft) override
{ {
if (cachedTypes->contains(ty) || seenWithPolarity(ty)) if (cachedTypes->contains(ty) || seenWithCurrentPolarity(ty))
return false; return false;
const bool oldValue = isWithinFunction;
isWithinFunction = true;
flip(); flip();
traverse(ft.argTypes); traverse(ft.argTypes);
flip(); flip();
traverse(ft.retTypes); traverse(ft.retTypes);
isWithinFunction = oldValue;
return false; return false;
} }
@ -500,24 +610,39 @@ struct FreeTypeSearcher : TypeVisitor
bool visit(TypePackId tp, const FreeTypePack& ftp) override bool visit(TypePackId tp, const FreeTypePack& ftp) override
{ {
if (seenWithPolarity(tp)) if (seenWithCurrentPolarity(tp))
return false; return false;
if (!subsumes(scope, ftp.scope)) if (!subsumes(scope, ftp.scope))
return true; return true;
switch (polarity) if (FFlag::LuauNonReentrantGeneralization)
{ {
case Positive: GeneralizationParams<TypePackId>& params = typePacks[tp];
positiveTypes[tp]++; ++params.useCount;
break;
case Negative: if (!isWithinFunction)
negativeTypes[tp]++; params.foundOutsideFunctions = true;
break;
case Both: params.polarity |= polarity;
positiveTypes[tp]++; }
negativeTypes[tp]++; else
break; {
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; return true;
@ -547,7 +672,7 @@ struct TypeCacher : TypeOnceVisitor
{ {
} }
void cache(TypeId ty) void cache(TypeId ty) const
{ {
cachedTypes->insert(ty); cachedTypes->insert(ty);
} }
@ -967,13 +1092,227 @@ struct TypeCacher : TypeOnceVisitor
} }
}; };
/**
* 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);
if (seen.find(haystack))
return haystack;
seen.insert(haystack);
if (const UnionType* ut = get<UnionType>(haystack))
{
OrderedSet<TypeId> newOptions;
for (TypeId option : ut)
{
if (option == needle)
continue;
if (get<NeverType>(option))
continue;
LUAU_ASSERT(!get<UnionType>(option));
if (get<IntersectionType>(option))
newOptions.insert(removeType(arena, builtinTypes, seen, option, needle));
else
newOptions.insert(option);
}
if (newOptions.empty())
return builtinTypes->neverType;
else if (newOptions.size() == 1)
{
TypeId onlyType = *newOptions.begin();
LUAU_ASSERT(onlyType != haystack);
return onlyType;
}
else
return arena->addType(UnionType{newOptions.takeVector()});
}
if (const IntersectionType* it = get<IntersectionType>(haystack))
{
OrderedSet<TypeId> newParts;
for (TypeId part : it)
{
part = follow(part);
if (part == needle)
continue;
if (get<UnknownType>(part))
continue;
LUAU_ASSERT(!get<IntersectionType>(follow(part)));
if (get<UnionType>(part))
newParts.insert(removeType(arena, builtinTypes, seen, part, needle));
else
newParts.insert(part);
}
if (newParts.empty())
return builtinTypes->unknownType;
else if (newParts.size() == 1)
{
TypeId onlyType = *newParts.begin();
LUAU_ASSERT(onlyType != needle);
return onlyType;
}
else
return arena->addType(IntersectionType{newParts.takeVector()});
}
return haystack;
}
std::optional<TypeId> generalizeType(
NotNull<TypeArena> arena,
NotNull<BuiltinTypes> builtinTypes,
NotNull<Scope> scope,
TypeId freeTy,
const GeneralizationParams<TypeId>& params
)
{
freeTy = follow(freeTy);
FreeType* ft = getMutable<FreeType>(freeTy);
LUAU_ASSERT(ft);
LUAU_ASSERT(isPositive(params.polarity) || isNegative(params.polarity));
const bool hasLowerBound = !get<NeverType>(follow(ft->lowerBound));
const bool hasUpperBound = !get<UnknownType>(follow(ft->upperBound));
const bool isWithinFunction = !params.foundOutsideFunctions;
if (!hasLowerBound && !hasUpperBound)
{
if ((params.polarity != Polarity::Mixed && params.useCount == 1) || !isWithinFunction)
emplaceType<BoundType>(asMutable(freeTy), builtinTypes->unknownType);
else
{
emplaceType<GenericType>(asMutable(freeTy), scope, params.polarity);
return freeTy;
}
}
// It is possible that this free type has other free types in its upper
// or lower bounds. If this is the case, we must replace those
// references with never (for the lower bound) or unknown (for the upper
// bound).
//
// If we do not do this, we get tautological bounds like a <: a <: unknown.
else if (isPositive(params.polarity) && !hasUpperBound)
{
TypeId lb = follow(ft->lowerBound);
if (FreeType* lowerFree = getMutable<FreeType>(lb); lowerFree && lowerFree->upperBound == freeTy)
lowerFree->upperBound = builtinTypes->unknownType;
else
{
DenseHashSet<TypeId> replaceSeen{nullptr};
lb = removeType(arena, builtinTypes, replaceSeen, lb, freeTy);
ft->lowerBound = lb;
}
if (follow(lb) != freeTy)
emplaceType<BoundType>(asMutable(freeTy), lb);
else if (!isWithinFunction || params.useCount == 1)
emplaceType<BoundType>(asMutable(freeTy), builtinTypes->unknownType);
else
{
// 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);
return freeTy;
}
}
else
{
TypeId ub = follow(ft->upperBound);
if (FreeType* upperFree = getMutable<FreeType>(ub); upperFree && upperFree->lowerBound == freeTy)
upperFree->lowerBound = builtinTypes->neverType;
else
{
// If the free type appears within its own upper bound, cull that cycle.
DenseHashSet<TypeId> replaceSeen{nullptr};
ub = removeType(arena, builtinTypes, replaceSeen, ub, freeTy);
ft->upperBound = ub;
}
if (follow(ub) != freeTy)
emplaceType<BoundType>(asMutable(freeTy), ub);
else if (!isWithinFunction || params.useCount == 1)
emplaceType<BoundType>(asMutable(freeTy), builtinTypes->unknownType);
else
{
// if the upper bound is the type in question, we don't actually have an upper bound.
emplaceType<GenericType>(asMutable(freeTy), scope, params.polarity);
return freeTy;
}
}
return std::nullopt;
}
std::optional<TypePackId> generalizeTypePack(
NotNull<TypeArena> arena,
NotNull<BuiltinTypes> builtinTypes,
NotNull<Scope> scope,
TypePackId tp,
const GeneralizationParams<TypePackId>& params
)
{
tp = follow(tp);
if (tp->owningArena != arena)
return std::nullopt;
const FreeTypePack* ftp = get<FreeTypePack>(tp);
if (!ftp)
return std::nullopt;
if (!subsumes(scope, ftp->scope))
return std::nullopt;
if (1 == params.useCount)
emplaceTypePack<BoundTypePack>(asMutable(tp), builtinTypes->unknownTypePack);
else
{
emplaceTypePack<GenericTypePack>(asMutable(tp), scope, params.polarity);
return tp;
}
return std::nullopt;
}
void sealTable(NotNull<Scope> scope, TypeId ty)
{
TableType* tableTy = getMutable<TableType>(follow(ty));
if (!tableTy)
return;
if (!subsumes(scope, tableTy->scope))
return;
if (tableTy->state == TableState::Unsealed || tableTy->state == TableState::Free)
tableTy->state = TableState::Sealed;
}
std::optional<TypeId> generalize( std::optional<TypeId> generalize(
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
bool avoidSealingTables
) )
{ {
ty = follow(ty); ty = follow(ty);
@ -984,35 +1323,74 @@ std::optional<TypeId> generalize(
FreeTypeSearcher fts{scope, cachedTypes}; FreeTypeSearcher fts{scope, cachedTypes};
fts.traverse(ty); fts.traverse(ty);
MutatingGeneralizer gen{arena, builtinTypes, scope, cachedTypes, std::move(fts.positiveTypes), std::move(fts.negativeTypes), avoidSealingTables}; if (FFlag::LuauNonReentrantGeneralization)
gen.traverse(ty);
/* MutatingGeneralizer mutates types in place, so it is possible that ty has
* been transmuted to a BoundType. We must follow it again and verify that
* we are allowed to mutate it before we attach generics to it.
*/
ty = follow(ty);
if (ty->owningArena != arena || ty->persistent)
return ty;
TypeCacher cacher{cachedTypes};
cacher.traverse(ty);
FunctionType* ftv = getMutable<FunctionType>(ty);
if (ftv)
{ {
// If we're generalizing a function type, add any of the newly inferred FunctionType* functionTy = getMutable<FunctionType>(ty);
// generics to the list of existing generic types. auto pushGeneric = [&](TypeId t)
for (const auto g : std::move(gen.generics))
{ {
ftv->generics.push_back(g); if (functionTy)
functionTy->generics.push_back(t);
};
auto pushGenericPack = [&](TypePackId tp)
{
if (functionTy)
functionTy->genericPacks.push_back(tp);
};
for (const auto& [freeTy, params] : fts.types)
{
if (std::optional<TypeId> genericTy = generalizeType(arena, builtinTypes, scope, freeTy, params))
pushGeneric(*genericTy);
} }
// Ditto for generic packs.
for (const auto gp : std::move(gen.genericPacks)) for (TypeId unsealedTableTy : fts.unsealedTables)
sealTable(scope, unsealedTableTy);
for (const auto& [freePackId, params] : fts.typePacks)
{ {
ftv->genericPacks.push_back(gp); TypePackId freePack = follow(freePackId);
std::optional<TypePackId> generalizedTp = generalizeTypePack(arena, builtinTypes, scope, freePack, params);
if (generalizedTp)
pushGenericPack(freePack);
}
TypeCacher cacher{cachedTypes};
cacher.traverse(ty);
}
else
{
MutatingGeneralizer gen{arena, builtinTypes, scope, cachedTypes, std::move(fts.positiveTypes), std::move(fts.negativeTypes)};
gen.traverse(ty);
/* MutatingGeneralizer mutates types in place, so it is possible that ty has
* been transmuted to a BoundType. We must follow it again and verify that
* we are allowed to mutate it before we attach generics to it.
*/
ty = follow(ty);
if (ty->owningArena != arena || ty->persistent)
return ty;
TypeCacher cacher{cachedTypes};
cacher.traverse(ty);
FunctionType* ftv = getMutable<FunctionType>(ty);
if (ftv)
{
// If we're generalizing a function type, add any of the newly inferred
// generics to the list of existing generic types.
for (const auto g : std::move(gen.generics))
{
ftv->generics.push_back(g);
}
// Ditto for generic packs.
for (const auto gp : std::move(gen.genericPacks))
{
ftv->genericPacks.push_back(gp);
}
} }
} }

View file

@ -9,6 +9,7 @@ GlobalTypes::GlobalTypes(NotNull<BuiltinTypes> builtinTypes)
: builtinTypes(builtinTypes) : builtinTypes(builtinTypes)
{ {
globalScope = std::make_shared<Scope>(globalTypes.addTypePack(TypePackVar{FreeTypePack{TypeLevel{}}})); globalScope = std::make_shared<Scope>(globalTypes.addTypePack(TypePackVar{FreeTypePack{TypeLevel{}}}));
globalTypeFunctionScope = std::make_shared<Scope>(globalTypes.addTypePack(TypePackVar{FreeTypePack{TypeLevel{}}}));
globalScope->addBuiltinTypeBinding("any", TypeFun{{}, builtinTypes->anyType}); globalScope->addBuiltinTypeBinding("any", TypeFun{{}, builtinTypes->anyType});
globalScope->addBuiltinTypeBinding("nil", TypeFun{{}, builtinTypes->nilType}); globalScope->addBuiltinTypeBinding("nil", TypeFun{{}, builtinTypes->nilType});

View file

@ -0,0 +1,169 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/DenseHash.h"
#include "Luau/Polarity.h"
#include "Luau/Scope.h"
#include "Luau/VisitType.h"
LUAU_FASTFLAG(LuauNonReentrantGeneralization)
namespace Luau
{
struct InferPolarity : TypeVisitor
{
NotNull<TypeArena> arena;
NotNull<Scope> scope;
DenseHashMap<TypeId, Polarity> types{nullptr};
DenseHashMap<TypePackId, Polarity> packs{nullptr};
Polarity polarity = Polarity::Positive;
explicit InferPolarity(NotNull<TypeArena> arena, NotNull<Scope> scope)
: arena(arena)
, scope(scope)
{
}
void flip()
{
polarity = invert(polarity);
}
bool visit(TypeId ty, const GenericType& gt) override
{
if (ty->owningArena != arena)
return false;
if (subsumes(scope, gt.scope))
types[ty] |= polarity;
return false;
}
bool visit(TypeId ty, const TableType& tt) override
{
if (ty->owningArena != arena)
return false;
const Polarity p = polarity;
for (const auto& [name, prop] : tt.props)
{
if (prop.isShared())
{
polarity = Polarity::Mixed;
traverse(prop.type());
}
else if (prop.isReadOnly())
{
polarity = p;
traverse(*prop.readTy);
}
else if (prop.isWriteOnly())
{
polarity = invert(p);
traverse(*prop.writeTy);
}
else
LUAU_ASSERT(!"Unreachable");
}
if (tt.indexer)
{
polarity = Polarity::Mixed;
traverse(tt.indexer->indexType);
traverse(tt.indexer->indexResultType);
}
polarity = p;
return false;
}
bool visit(TypeId ty, const FunctionType& ft) override
{
if (ty->owningArena != arena)
return false;
const Polarity p = polarity;
polarity = Polarity::Positive;
// If these types actually occur within the function signature, their
// polarity will be overwritten. If not, we infer that they are phantom
// types.
for (TypeId generic : ft.generics)
{
const auto gen = get<GenericType>(generic);
LUAU_ASSERT(gen);
if (subsumes(scope, gen->scope))
types[generic] = Polarity::None;
}
for (TypePackId genericPack : ft.genericPacks)
{
const auto gen = get<GenericTypePack>(genericPack);
LUAU_ASSERT(gen);
if (subsumes(scope, gen->scope))
packs[genericPack] = Polarity::None;
}
flip();
traverse(ft.argTypes);
flip();
traverse(ft.retTypes);
polarity = p;
return false;
}
bool visit(TypeId, const ClassType&) override
{
return false;
}
bool visit(TypePackId tp, const GenericTypePack& gtp) override
{
packs[tp] |= polarity;
return false;
}
};
template<typename TID>
static void inferGenericPolarities_(NotNull<TypeArena> arena, NotNull<Scope> scope, TID ty)
{
if (!FFlag::LuauNonReentrantGeneralization)
return;
InferPolarity infer{arena, scope};
infer.traverse(ty);
for (const auto& [ty, polarity] : infer.types)
{
auto gt = getMutable<GenericType>(ty);
LUAU_ASSERT(gt);
gt->polarity = polarity;
}
for (const auto& [tp, polarity] : infer.packs)
{
if (tp->owningArena != arena)
continue;
auto gp = getMutable<GenericTypePack>(tp);
LUAU_ASSERT(gp);
gp->polarity = polarity;
}
}
void inferGenericPolarities(NotNull<TypeArena> arena, NotNull<Scope> scope, TypeId ty)
{
inferGenericPolarities_(arena, scope, ty);
}
void inferGenericPolarities(NotNull<TypeArena> arena, NotNull<Scope> scope, TypePackId tp)
{
inferGenericPolarities_(arena, scope, tp);
}
} // namespace Luau

View file

@ -61,7 +61,7 @@ TypeId Instantiation::clean(TypeId ty)
const FunctionType* ftv = log->getMutable<FunctionType>(ty); const FunctionType* ftv = log->getMutable<FunctionType>(ty);
LUAU_ASSERT(ftv); LUAU_ASSERT(ftv);
FunctionType clone = FunctionType{level, scope, ftv->argTypes, ftv->retTypes, ftv->definition, ftv->hasSelf}; FunctionType clone = FunctionType{level, ftv->argTypes, ftv->retTypes, ftv->definition, ftv->hasSelf};
clone.magic = ftv->magic; clone.magic = ftv->magic;
clone.tags = ftv->tags; clone.tags = ftv->tags;
clone.argNames = ftv->argNames; clone.argNames = ftv->argNames;

View file

@ -19,6 +19,8 @@ LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauAttribute) LUAU_FASTFLAG(LuauAttribute)
LUAU_FASTFLAGVARIABLE(LintRedundantNativeAttribute) LUAU_FASTFLAGVARIABLE(LintRedundantNativeAttribute)
LUAU_FASTFLAG(LuauDeprecatedAttribute)
namespace Luau namespace Luau
{ {
@ -2280,6 +2282,57 @@ private:
{ {
} }
bool visit(AstExprLocal* node) override
{
if (FFlag::LuauDeprecatedAttribute)
{
const FunctionType* fty = getFunctionType(node);
bool shouldReport = fty && fty->isDeprecatedFunction && !inScope(fty);
if (shouldReport)
report(node->location, node->local->name.value);
}
return true;
}
bool visit(AstExprGlobal* node) override
{
if (FFlag::LuauDeprecatedAttribute)
{
const FunctionType* fty = getFunctionType(node);
bool shouldReport = fty && fty->isDeprecatedFunction && !inScope(fty);
if (shouldReport)
report(node->location, node->name.value);
}
return true;
}
bool visit(AstStatLocalFunction* node) override
{
if (FFlag::LuauDeprecatedAttribute)
{
check(node->func);
return false;
}
else
return true;
}
bool visit(AstStatFunction* node) override
{
if (FFlag::LuauDeprecatedAttribute)
{
check(node->func);
return false;
}
else
return true;
}
bool visit(AstExprIndexName* node) override bool visit(AstExprIndexName* node) override
{ {
if (std::optional<TypeId> ty = context->getType(node->expr)) if (std::optional<TypeId> ty = context->getType(node->expr))
@ -2325,18 +2378,59 @@ private:
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);
else if (FFlag::LuauDeprecatedAttribute && prop)
{
if (std::optional<TypeId> ty = prop->readTy)
{
const FunctionType* fty = get<FunctionType>(follow(ty));
bool shouldReport = fty && fty->isDeprecatedFunction && !inScope(fty);
if (shouldReport)
{
const char* className = nullptr;
if (AstExprGlobal* global = node->expr->as<AstExprGlobal>())
className = global->name.value;
const char* functionName = node->index.value;
report(node->location, className, functionName);
}
}
}
} }
else if (const TableType* tty = get<TableType>(ty)) else if (const TableType* tty = get<TableType>(ty))
{ {
auto prop = tty->props.find(node->index.value); auto prop = tty->props.find(node->index.value);
if (prop != tty->props.end() && prop->second.deprecated) if (prop != tty->props.end())
{ {
// strip synthetic typeof() for builtin tables if (prop->second.deprecated)
if (tty->name && tty->name->compare(0, 7, "typeof(") == 0 && tty->name->back() == ')') {
report(node->location, prop->second, tty->name->substr(7, tty->name->length() - 8).c_str(), node->index.value); // strip synthetic typeof() for builtin tables
else if (tty->name && tty->name->compare(0, 7, "typeof(") == 0 && tty->name->back() == ')')
report(node->location, prop->second, tty->name ? tty->name->c_str() : nullptr, node->index.value); report(node->location, prop->second, tty->name->substr(7, tty->name->length() - 8).c_str(), node->index.value);
else
report(node->location, prop->second, tty->name ? tty->name->c_str() : nullptr, node->index.value);
}
else if (FFlag::LuauDeprecatedAttribute)
{
if (std::optional<TypeId> ty = prop->second.readTy)
{
const FunctionType* fty = get<FunctionType>(follow(ty));
bool shouldReport = fty && fty->isDeprecatedFunction && !inScope(fty);
if (shouldReport)
{
const char* className = nullptr;
if (AstExprGlobal* global = node->expr->as<AstExprGlobal>())
className = global->name.value;
const char* functionName = node->index.value;
report(node->location, className, functionName);
}
}
}
} }
} }
} }
@ -2355,6 +2449,26 @@ private:
} }
} }
void check(AstExprFunction* func)
{
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
LUAU_ASSERT(func);
const FunctionType* fty = getFunctionType(func);
bool isDeprecated = fty && fty->isDeprecatedFunction;
// If a function is deprecated, we don't want to flag its recursive uses.
// So we push it on a stack while its body is being analyzed.
// When a deprecated function is used, we check the stack to ensure that we are not inside that function.
if (isDeprecated)
pushScope(fty);
func->visit(this);
if (isDeprecated)
popScope(fty);
}
void report(const Location& location, const Property& prop, const char* container, const char* field) void report(const Location& location, const Property& prop, const char* container, const char* field)
{ {
std::string suggestion = prop.deprecatedSuggestion.empty() ? "" : format(", use '%s' instead", prop.deprecatedSuggestion.c_str()); std::string suggestion = prop.deprecatedSuggestion.empty() ? "" : format(", use '%s' instead", prop.deprecatedSuggestion.c_str());
@ -2364,6 +2478,63 @@ private:
else else
emitWarning(*context, LintWarning::Code_DeprecatedApi, location, "Member '%s' is deprecated%s", field, suggestion.c_str()); emitWarning(*context, LintWarning::Code_DeprecatedApi, location, "Member '%s' is deprecated%s", field, suggestion.c_str());
} }
void report(const Location& location, const char* tableName, const char* functionName)
{
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
if (tableName)
emitWarning(*context, LintWarning::Code_DeprecatedApi, location, "Member '%s.%s' is deprecated", tableName, functionName);
else
emitWarning(*context, LintWarning::Code_DeprecatedApi, location, "Member '%s' is deprecated", functionName);
}
void report(const Location& location, const char* functionName)
{
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
emitWarning(*context, LintWarning::Code_DeprecatedApi, location, "Function '%s' is deprecated", functionName);
}
std::vector<const FunctionType*> functionTypeScopeStack;
void pushScope(const FunctionType* fty)
{
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
LUAU_ASSERT(fty);
functionTypeScopeStack.push_back(fty);
}
void popScope(const FunctionType* fty)
{
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
LUAU_ASSERT(fty);
LUAU_ASSERT(fty == functionTypeScopeStack.back());
functionTypeScopeStack.pop_back();
}
bool inScope(const FunctionType* fty) const
{
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
LUAU_ASSERT(fty);
return std::find(functionTypeScopeStack.begin(), functionTypeScopeStack.end(), fty) != functionTypeScopeStack.end();
}
const FunctionType* getFunctionType(AstExpr* node)
{
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
std::optional<TypeId> ty = context->getType(node);
if (!ty)
return nullptr;
const FunctionType* fty = get<FunctionType>(follow(ty));
return fty;
}
}; };
class LintTableOperations : AstVisitor class LintTableOperations : AstVisitor

View file

@ -15,12 +15,12 @@
#include <algorithm> #include <algorithm>
LUAU_FASTFLAG(LuauSolverV2); LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTFLAGVARIABLE(LuauIncrementalAutocompleteCommentDetection) LUAU_FASTFLAG(LuauRetainDefinitionAliasLocations)
namespace Luau namespace Luau
{ {
static void defaultLogLuau(std::string_view input) static void defaultLogLuau(std::string_view context, std::string_view input)
{ {
// The default is to do nothing because we don't want to mess with // The default is to do nothing because we don't want to mess with
// the xml parsing done by the dcr script. // the xml parsing done by the dcr script.
@ -38,21 +38,6 @@ void resetLogLuauProc()
logLuau = &defaultLogLuau; logLuau = &defaultLogLuau;
} }
static bool contains_DEPRECATED(Position pos, Comment comment)
{
if (comment.location.contains(pos))
return true;
else if (comment.type == Lexeme::BrokenComment && comment.location.begin <= pos) // Broken comments are broken specifically because they don't
// have an end
return true;
else if (comment.type == Lexeme::Comment && comment.location.end == pos)
return true;
else
return false;
}
static bool contains(Position pos, Comment comment) static bool contains(Position pos, Comment comment)
{ {
if (comment.location.contains(pos)) if (comment.location.contains(pos))
@ -76,11 +61,8 @@ bool isWithinComment(const std::vector<Comment>& commentLocations, Position pos)
Comment{Lexeme::Comment, Location{pos, pos}}, Comment{Lexeme::Comment, Location{pos, pos}},
[](const Comment& a, const Comment& b) [](const Comment& a, const Comment& b)
{ {
if (FFlag::LuauIncrementalAutocompleteCommentDetection) if (a.type == Lexeme::Comment)
{ return a.location.end.line < b.location.end.line;
if (a.type == Lexeme::Comment)
return a.location.end.line < b.location.end.line;
}
return a.location.end < b.location.end; return a.location.end < b.location.end;
} }
); );
@ -88,7 +70,7 @@ bool isWithinComment(const std::vector<Comment>& commentLocations, Position pos)
if (iter == commentLocations.end()) if (iter == commentLocations.end())
return false; return false;
if (FFlag::LuauIncrementalAutocompleteCommentDetection ? contains(pos, *iter) : contains_DEPRECATED(pos, *iter)) if (contains(pos, *iter))
return true; return true;
// Due to the nature of std::lower_bound, it is possible that iter points at a comment that ends // Due to the nature of std::lower_bound, it is possible that iter points at a comment that ends
@ -172,8 +154,6 @@ struct ClonePublicInterface : Substitution
} }
ftv->level = TypeLevel{0, 0}; ftv->level = TypeLevel{0, 0};
if (FFlag::LuauSolverV2)
ftv->scope = nullptr;
} }
else if (TableType* ttv = getMutable<TableType>(result)) else if (TableType* ttv = getMutable<TableType>(result))
{ {
@ -285,7 +265,10 @@ struct ClonePublicInterface : Substitution
TypeId type = cloneType(tf.type); TypeId type = cloneType(tf.type);
return TypeFun{typeParams, typePackParams, type}; if (FFlag::LuauRetainDefinitionAliasLocations)
return TypeFun{typeParams, typePackParams, type, tf.definitionLocation};
else
return TypeFun{typeParams, typePackParams, type};
} }
}; };

View file

@ -2,6 +2,7 @@
#include "Luau/NonStrictTypeChecker.h" #include "Luau/NonStrictTypeChecker.h"
#include "Luau/Ast.h" #include "Luau/Ast.h"
#include "Luau/AstQuery.h"
#include "Luau/Common.h" #include "Luau/Common.h"
#include "Luau/Simplify.h" #include "Luau/Simplify.h"
#include "Luau/Type.h" #include "Luau/Type.h"
@ -22,6 +23,7 @@
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds) LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
LUAU_FASTFLAGVARIABLE(LuauNonStrictVisitorImprovements) LUAU_FASTFLAGVARIABLE(LuauNonStrictVisitorImprovements)
LUAU_FASTFLAGVARIABLE(LuauNewNonStrictWarnOnUnknownGlobals) LUAU_FASTFLAGVARIABLE(LuauNewNonStrictWarnOnUnknownGlobals)
LUAU_FASTFLAGVARIABLE(LuauNonStrictFuncDefErrorFix)
namespace Luau namespace Luau
{ {
@ -763,7 +765,17 @@ 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))
reportError(NonStrictFunctionDefinitionError{exprFn->debugname.value, local->name.value, *ty}, local->location); {
if (FFlag::LuauNonStrictFuncDefErrorFix)
{
const char* debugname = exprFn->debugname.value;
reportError(NonStrictFunctionDefinitionError{debugname ? debugname : "", local->name.value, *ty}, local->location);
}
else
{
reportError(NonStrictFunctionDefinitionError{exprFn->debugname.value, local->name.value, *ty}, local->location);
}
}
remainder.remove(dfg->getDef(local)); remainder.remove(dfg->getDef(local));
} }
return remainder; return remainder;

View file

@ -17,11 +17,16 @@
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_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAGVARIABLE(LuauFixInfiniteRecursionInNormalization) LUAU_FASTFLAGVARIABLE(LuauFixInfiniteRecursionInNormalization)
LUAU_FASTFLAGVARIABLE(LuauFixNormalizedIntersectionOfNegatedClass) LUAU_FASTFLAGVARIABLE(LuauNormalizedBufferIsNotUnknown)
LUAU_FASTFLAGVARIABLE(LuauNormalizeLimitFunctionSet)
LUAU_FASTFLAGVARIABLE(LuauNormalizationCatchMetatableCycles)
namespace Luau namespace Luau
{ {
@ -303,7 +308,9 @@ 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() && isPrim(threads, PrimitiveType::Thread) && isThread(threads); strings.isString() &&
(FFlag::LuauNormalizedBufferIsNotUnknown ? isThread(threads) && isBuffer(buffers)
: isPrim(threads, PrimitiveType::Thread) && isThread(threads));
// Check is class // Check is class
bool isTopClass = false; bool isTopClass = false;
@ -578,7 +585,7 @@ NormalizationResult Normalizer::isIntersectionInhabited(TypeId left, TypeId righ
{ {
left = follow(left); left = follow(left);
right = follow(right); right = follow(right);
// We're asking if intersection is inahbited between left and right but we've already seen them .... // We're asking if intersection is inhabited between left and right but we've already seen them ....
if (cacheInhabitance) if (cacheInhabitance)
{ {
@ -1684,6 +1691,13 @@ NormalizationResult Normalizer::unionNormals(NormalizedType& here, const Normali
return res; return res;
} }
if (FFlag::LuauNormalizeLimitFunctionSet)
{
// Limit based on worst-case expansion of the function unions
if (here.functions.parts.size() * there.functions.parts.size() >= size_t(FInt::LuauNormalizeUnionLimit))
return NormalizationResult::HitLimits;
}
here.booleans = unionOfBools(here.booleans, there.booleans); here.booleans = unionOfBools(here.booleans, there.booleans);
unionClasses(here.classes, there.classes); unionClasses(here.classes, there.classes);
@ -1695,6 +1709,7 @@ NormalizationResult Normalizer::unionNormals(NormalizedType& here, const Normali
here.buffers = (get<NeverType>(there.buffers) ? here.buffers : there.buffers); here.buffers = (get<NeverType>(there.buffers) ? here.buffers : there.buffers);
unionFunctions(here.functions, there.functions); unionFunctions(here.functions, there.functions);
unionTables(here.tables, there.tables); unionTables(here.tables, there.tables);
return NormalizationResult::True; return NormalizationResult::True;
} }
@ -1734,7 +1749,7 @@ NormalizationResult Normalizer::intersectNormalWithNegationTy(TypeId toNegate, N
return NormalizationResult::True; return NormalizationResult::True;
} }
// See above for an explaination of `ignoreSmallerTyvars`. // See above for an explanation of `ignoreSmallerTyvars`.
NormalizationResult Normalizer::unionNormalWithTy( NormalizationResult Normalizer::unionNormalWithTy(
NormalizedType& here, NormalizedType& here,
TypeId there, TypeId there,
@ -2288,7 +2303,7 @@ void Normalizer::intersectClassesWithClass(NormalizedClassType& heres, TypeId th
for (auto nIt = negations.begin(); nIt != negations.end();) for (auto nIt = negations.begin(); nIt != negations.end();)
{ {
if (FFlag::LuauFixNormalizedIntersectionOfNegatedClass && isSubclass(there, *nIt)) if (isSubclass(there, *nIt))
{ {
// Hitting this block means that the incoming class is a // Hitting this block means that the incoming class is a
// subclass of this type, _and_ one of its negations is a // subclass of this type, _and_ one of its negations is a
@ -3049,7 +3064,7 @@ NormalizationResult Normalizer::intersectTyvarsWithTy(
return NormalizationResult::True; return NormalizationResult::True;
} }
// See above for an explaination of `ignoreSmallerTyvars`. // See above for an explanation of `ignoreSmallerTyvars`.
NormalizationResult Normalizer::intersectNormals(NormalizedType& here, const NormalizedType& there, int ignoreSmallerTyvars) NormalizationResult Normalizer::intersectNormals(NormalizedType& here, const NormalizedType& there, int ignoreSmallerTyvars)
{ {
RecursionCounter _rc(&sharedState->counters.recursionCount); RecursionCounter _rc(&sharedState->counters.recursionCount);
@ -3067,11 +3082,17 @@ NormalizationResult Normalizer::intersectNormals(NormalizedType& here, const Nor
return unionNormals(here, there, ignoreSmallerTyvars); return unionNormals(here, there, ignoreSmallerTyvars);
} }
// Limit based on worst-case expansion of the table intersection // Limit based on worst-case expansion of the table/function intersections
// This restriction can be relaxed when table intersection simplification is improved // This restriction can be relaxed when table intersection simplification is improved
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))
return NormalizationResult::HitLimits;
}
here.booleans = intersectionOfBools(here.booleans, there.booleans); here.booleans = intersectionOfBools(here.booleans, there.booleans);
intersectClasses(here.classes, there.classes); intersectClasses(here.classes, there.classes);
@ -3207,7 +3228,7 @@ NormalizationResult Normalizer::intersectNormalWithTy(
{ {
TypeId errors = here.errors; TypeId errors = here.errors;
clearNormal(here); clearNormal(here);
here.errors = errors; here.errors = FFlag::LuauNormalizeIntersectErrorToAnError && get<ErrorType>(errors) ? errors : there;
} }
else if (const PrimitiveType* ptv = get<PrimitiveType>(there)) else if (const PrimitiveType* ptv = get<PrimitiveType>(there))
{ {
@ -3304,8 +3325,18 @@ NormalizationResult Normalizer::intersectNormalWithTy(
clearNormal(here); clearNormal(here);
return NormalizationResult::True; return NormalizationResult::True;
} }
else if (FFlag::LuauNormalizeNegatedErrorToAnError && get<ErrorType>(t))
{
// ~error is still an error, so intersecting with the negation is the same as intersecting with a type
TypeId errors = here.errors;
clearNormal(here);
here.errors = FFlag::LuauNormalizeIntersectErrorToAnError && get<ErrorType>(errors) ? errors : t;
}
else if (auto nt = get<NegationType>(t)) else if (auto nt = get<NegationType>(t))
{
here.tyvars = std::move(tyvars);
return intersectNormalWithTy(here, nt->ty, seenTablePropPairs, seenSetTypes); return intersectNormalWithTy(here, nt->ty, seenTablePropPairs, seenSetTypes);
}
else else
{ {
// TODO negated unions, intersections, table, and function. // TODO negated unions, intersections, table, and function.
@ -3333,7 +3364,7 @@ NormalizationResult Normalizer::intersectNormalWithTy(
return NormalizationResult::True; return NormalizationResult::True;
} }
void makeTableShared(TypeId ty) void makeTableShared_DEPRECATED(TypeId ty)
{ {
ty = follow(ty); ty = follow(ty);
if (auto tableTy = getMutable<TableType>(ty)) if (auto tableTy = getMutable<TableType>(ty))
@ -3343,11 +3374,35 @@ void makeTableShared(TypeId ty)
} }
else if (auto metatableTy = get<MetatableType>(ty)) else if (auto metatableTy = get<MetatableType>(ty))
{ {
makeTableShared(metatableTy->metatable); makeTableShared_DEPRECATED(metatableTy->metatable);
makeTableShared(metatableTy->table); makeTableShared_DEPRECATED(metatableTy->table);
} }
} }
void makeTableShared(TypeId ty, DenseHashSet<TypeId>& seen)
{
ty = follow(ty);
if (seen.contains(ty))
return;
seen.insert(ty);
if (auto tableTy = getMutable<TableType>(ty))
{
for (auto& [_, prop] : tableTy->props)
prop.makeShared();
}
else if (auto metatableTy = get<MetatableType>(ty))
{
makeTableShared(metatableTy->metatable, seen);
makeTableShared(metatableTy->table, seen);
}
}
void makeTableShared(TypeId ty)
{
DenseHashSet<TypeId> seen{nullptr};
makeTableShared(ty, seen);
}
// -------- Convert back from a normalized type to a type // -------- Convert back from a normalized type to a type
TypeId Normalizer::typeFromNormal(const NormalizedType& norm) TypeId Normalizer::typeFromNormal(const NormalizedType& norm)
{ {
@ -3447,7 +3502,10 @@ TypeId Normalizer::typeFromNormal(const NormalizedType& norm)
result.reserve(result.size() + norm.tables.size()); result.reserve(result.size() + norm.tables.size());
for (auto table : norm.tables) for (auto table : norm.tables)
{ {
makeTableShared(table); if (FFlag::LuauNormalizationCatchMetatableCycles)
makeTableShared(table);
else
makeTableShared_DEPRECATED(table);
result.push_back(table); result.push_back(table);
} }
} }

View file

@ -454,7 +454,7 @@ SolveResult solveFunctionCall(
TypePackId resultPack = arena->freshTypePack(scope); TypePackId resultPack = arena->freshTypePack(scope);
TypeId inferredTy = arena->addType(FunctionType{TypeLevel{}, scope.get(), argsPack, resultPack}); TypeId inferredTy = arena->addType(FunctionType{TypeLevel{}, argsPack, resultPack});
Unifier2 u2{NotNull{arena}, builtinTypes, scope, iceReporter}; Unifier2 u2{NotNull{arena}, builtinTypes, scope, iceReporter};
const bool occursCheckPassed = u2.unify(*overloadToUse, inferredTy); const bool occursCheckPassed = u2.unify(*overloadToUse, inferredTy);

View file

@ -107,134 +107,4 @@ void quantify(TypeId ty, TypeLevel level)
ftv->genericPacks.insert(ftv->genericPacks.end(), q.genericPacks.begin(), q.genericPacks.end()); ftv->genericPacks.insert(ftv->genericPacks.end(), q.genericPacks.begin(), q.genericPacks.end());
} }
struct PureQuantifier : Substitution
{
Scope* scope;
OrderedMap<TypeId, TypeId> insertedGenerics;
OrderedMap<TypePackId, TypePackId> insertedGenericPacks;
bool seenMutableType = false;
bool seenGenericType = false;
PureQuantifier(TypeArena* arena, Scope* scope)
: Substitution(TxnLog::empty(), arena)
, scope(scope)
{
}
bool isDirty(TypeId ty) override
{
LUAU_ASSERT(ty == follow(ty));
if (auto ftv = get<FreeType>(ty))
{
bool result = subsumes(scope, ftv->scope);
seenMutableType |= result;
return result;
}
else if (auto ttv = get<TableType>(ty))
{
if (ttv->state == TableState::Free)
seenMutableType = true;
else if (ttv->state == TableState::Generic)
seenGenericType = true;
return (ttv->state == TableState::Unsealed || ttv->state == TableState::Free) && subsumes(scope, ttv->scope);
}
return false;
}
bool isDirty(TypePackId tp) override
{
if (auto ftp = get<FreeTypePack>(tp))
{
return subsumes(scope, ftp->scope);
}
return false;
}
TypeId clean(TypeId ty) override
{
if (auto ftv = get<FreeType>(ty))
{
TypeId result = arena->addType(GenericType{scope});
insertedGenerics.push(ty, result);
return result;
}
else if (auto ttv = get<TableType>(ty))
{
TypeId result = arena->addType(TableType{});
TableType* resultTable = getMutable<TableType>(result);
LUAU_ASSERT(resultTable);
*resultTable = *ttv;
resultTable->level = TypeLevel{};
resultTable->scope = scope;
if (ttv->state == TableState::Free)
{
resultTable->state = TableState::Generic;
insertedGenerics.push(ty, result);
}
else if (ttv->state == TableState::Unsealed)
resultTable->state = TableState::Sealed;
return result;
}
return ty;
}
TypePackId clean(TypePackId tp) override
{
if (auto ftp = get<FreeTypePack>(tp))
{
TypePackId result = arena->addTypePack(TypePackVar{GenericTypePack{scope}});
insertedGenericPacks.push(tp, result);
return result;
}
return tp;
}
bool ignoreChildren(TypeId ty) override
{
if (get<ClassType>(ty))
return true;
return ty->persistent;
}
bool ignoreChildren(TypePackId ty) override
{
return ty->persistent;
}
};
std::optional<QuantifierResult> quantify(TypeArena* arena, TypeId ty, Scope* scope)
{
PureQuantifier quantifier{arena, scope};
std::optional<TypeId> result = quantifier.substitute(ty);
if (!result)
return std::nullopt;
FunctionType* ftv = getMutable<FunctionType>(*result);
LUAU_ASSERT(ftv);
ftv->scope = scope;
for (auto k : quantifier.insertedGenerics.keys)
{
TypeId g = quantifier.insertedGenerics.pairings[k];
if (get<GenericType>(g))
ftv->generics.push_back(g);
}
for (auto k : quantifier.insertedGenericPacks.keys)
ftv->genericPacks.push_back(quantifier.insertedGenericPacks.pairings[k]);
ftv->hasNoFreeOrGenericTypes = ftv->generics.empty() && ftv->genericPacks.empty() && !quantifier.seenGenericType && !quantifier.seenMutableType;
return std::optional<QuantifierResult>({*result, std::move(quantifier.insertedGenerics), std::move(quantifier.insertedGenericPacks)});
}
} // namespace Luau } // namespace Luau

View file

@ -54,7 +54,15 @@ RefinementId RefinementArena::proposition(const RefinementKey* key, TypeId discr
if (!key) if (!key)
return nullptr; return nullptr;
return NotNull{allocator.allocate(Proposition{key, discriminantTy})}; return NotNull{allocator.allocate(Proposition{key, discriminantTy, false})};
}
RefinementId RefinementArena::implicitProposition(const RefinementKey* key, TypeId discriminantTy)
{
if (!key)
return nullptr;
return NotNull{allocator.allocate(Proposition{key, discriminantTy, true})};
} }
} // namespace Luau } // namespace Luau

View file

@ -4,8 +4,6 @@
#include "Luau/Ast.h" #include "Luau/Ast.h"
#include "Luau/Module.h" #include "Luau/Module.h"
LUAU_FASTFLAGVARIABLE(LuauExtendedSimpleRequire)
namespace Luau namespace Luau
{ {
@ -106,96 +104,50 @@ struct RequireTracer : AstVisitor
{ {
ModuleInfo moduleContext{currentModuleName}; ModuleInfo moduleContext{currentModuleName};
if (FFlag::LuauExtendedSimpleRequire) // seed worklist with require arguments
work.reserve(requireCalls.size());
for (AstExprCall* require : requireCalls)
work.push_back(require->args.data[0]);
// push all dependent expressions to the work stack; note that the vector is modified during traversal
for (size_t i = 0; i < work.size(); ++i)
{ {
// seed worklist with require arguments if (AstNode* dep = getDependent(work[i]))
work.reserve(requireCalls.size()); work.push_back(dep);
for (AstExprCall* require : requireCalls)
work.push_back(require->args.data[0]);
// push all dependent expressions to the work stack; note that the vector is modified during traversal
for (size_t i = 0; i < work.size(); ++i)
{
if (AstNode* dep = getDependent(work[i]))
work.push_back(dep);
}
// resolve all expressions to a module info
for (size_t i = work.size(); i > 0; --i)
{
AstNode* expr = work[i - 1];
// when multiple expressions depend on the same one we push it to work queue multiple times
if (result.exprs.contains(expr))
continue;
std::optional<ModuleInfo> info;
if (AstNode* dep = getDependent(expr))
{
const ModuleInfo* context = result.exprs.find(dep);
if (context && expr->is<AstExprLocal>())
info = *context; // locals just inherit their dependent context, no resolution required
else if (context && (expr->is<AstExprGroup>() || expr->is<AstTypeGroup>()))
info = *context; // simple group nodes propagate their value
else if (context && (expr->is<AstTypeTypeof>() || expr->is<AstExprTypeAssertion>()))
info = *context; // typeof type annotations will resolve to the typeof content
else if (AstExpr* asExpr = expr->asExpr())
info = fileResolver->resolveModule(context, asExpr);
}
else if (AstExpr* asExpr = expr->asExpr())
{
info = fileResolver->resolveModule(&moduleContext, asExpr);
}
if (info)
result.exprs[expr] = std::move(*info);
}
} }
else
// resolve all expressions to a module info
for (size_t i = work.size(); i > 0; --i)
{ {
// seed worklist with require arguments AstNode* expr = work[i - 1];
work_DEPRECATED.reserve(requireCalls.size());
for (AstExprCall* require : requireCalls) // when multiple expressions depend on the same one we push it to work queue multiple times
work_DEPRECATED.push_back(require->args.data[0]); if (result.exprs.contains(expr))
continue;
// push all dependent expressions to the work stack; note that the vector is modified during traversal std::optional<ModuleInfo> info;
for (size_t i = 0; i < work_DEPRECATED.size(); ++i)
if (AstExpr* dep = getDependent_DEPRECATED(work_DEPRECATED[i]))
work_DEPRECATED.push_back(dep);
// resolve all expressions to a module info if (AstNode* dep = getDependent(expr))
for (size_t i = work_DEPRECATED.size(); i > 0; --i)
{ {
AstExpr* expr = work_DEPRECATED[i - 1]; const ModuleInfo* context = result.exprs.find(dep);
// when multiple expressions depend on the same one we push it to work queue multiple times if (context && expr->is<AstExprLocal>())
if (result.exprs.contains(expr)) info = *context; // locals just inherit their dependent context, no resolution required
continue; else if (context && (expr->is<AstExprGroup>() || expr->is<AstTypeGroup>()))
info = *context; // simple group nodes propagate their value
std::optional<ModuleInfo> info; else if (context && (expr->is<AstTypeTypeof>() || expr->is<AstExprTypeAssertion>()))
info = *context; // typeof type annotations will resolve to the typeof content
if (AstExpr* dep = getDependent_DEPRECATED(expr)) else if (AstExpr* asExpr = expr->asExpr())
{ info = fileResolver->resolveModule(context, asExpr);
const ModuleInfo* context = result.exprs.find(dep);
// locals just inherit their dependent context, no resolution required
if (expr->is<AstExprLocal>())
info = context ? std::optional<ModuleInfo>(*context) : std::nullopt;
else
info = fileResolver->resolveModule(context, expr);
}
else
{
info = fileResolver->resolveModule(&moduleContext, expr);
}
if (info)
result.exprs[expr] = std::move(*info);
} }
else if (AstExpr* asExpr = expr->asExpr())
{
info = fileResolver->resolveModule(&moduleContext, asExpr);
}
if (info)
result.exprs[expr] = std::move(*info);
} }
// resolve all requires according to their argument // resolve all requires according to their argument
@ -224,7 +176,6 @@ struct RequireTracer : AstVisitor
ModuleName currentModuleName; ModuleName currentModuleName;
DenseHashMap<AstLocal*, AstExpr*> locals; DenseHashMap<AstLocal*, AstExpr*> locals;
std::vector<AstExpr*> work_DEPRECATED;
std::vector<AstNode*> work; std::vector<AstNode*> work;
std::vector<AstExprCall*> requireCalls; std::vector<AstExprCall*> requireCalls;
}; };

View file

@ -84,6 +84,17 @@ std::optional<TypeId> Scope::lookupUnrefinedType(DefId def) const
return std::nullopt; return std::nullopt;
} }
std::optional<TypeId> Scope::lookupRValueRefinementType(DefId def) const
{
for (const Scope* current = this; current; current = current->parent.get())
{
if (auto ty = current->rvalueRefinements.find(def))
return *ty;
}
return std::nullopt;
}
std::optional<TypeId> Scope::lookup(DefId def) const std::optional<TypeId> Scope::lookup(DefId def) const
{ {
for (const Scope* current = this; current; current = current->parent.get()) for (const Scope* current = this; current; current = current->parent.get())
@ -181,6 +192,29 @@ std::optional<Binding> Scope::linearSearchForBinding(const std::string& name, bo
return std::nullopt; return std::nullopt;
} }
std::optional<std::pair<Symbol, Binding>> Scope::linearSearchForBindingPair(const std::string& name, bool traverseScopeChain) const
{
const Scope* scope = this;
while (scope)
{
for (auto& [n, binding] : scope->bindings)
{
if (n.local && n.local->name == name.c_str())
return {{n, binding}};
else if (n.global.value && n.global == name.c_str())
return {{n, binding}};
}
scope = scope->parent.get();
if (!traverseScopeChain)
break;
}
return std::nullopt;
}
// Updates the `this` scope with the assignments from the `childScope` including ones that doesn't exist in `this`. // Updates the `this` scope with the assignments from the `childScope` including ones that doesn't exist in `this`.
void Scope::inheritAssignments(const ScopePtr& childScope) void Scope::inheritAssignments(const ScopePtr& childScope)
{ {

View file

@ -13,6 +13,7 @@ LUAU_FASTINTVARIABLE(LuauTarjanChildLimit, 10000)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTINTVARIABLE(LuauTarjanPreallocationSize, 256) LUAU_FASTINTVARIABLE(LuauTarjanPreallocationSize, 256)
LUAU_FASTFLAG(LuauSyntheticErrors) LUAU_FASTFLAG(LuauSyntheticErrors)
LUAU_FASTFLAG(LuauDeprecatedAttribute)
namespace Luau namespace Luau
{ {
@ -95,13 +96,15 @@ static TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log, bool a
return dest.addType(a); return dest.addType(a);
else if constexpr (std::is_same_v<T, FunctionType>) else if constexpr (std::is_same_v<T, FunctionType>)
{ {
FunctionType clone = FunctionType{a.level, a.scope, a.argTypes, a.retTypes, a.definition, a.hasSelf}; FunctionType clone = FunctionType{a.level, a.argTypes, a.retTypes, a.definition, a.hasSelf};
clone.generics = a.generics; clone.generics = a.generics;
clone.genericPacks = a.genericPacks; clone.genericPacks = a.genericPacks;
clone.magic = a.magic; clone.magic = a.magic;
clone.tags = a.tags; clone.tags = a.tags;
clone.argNames = a.argNames; clone.argNames = a.argNames;
clone.isCheckedFunction = a.isCheckedFunction; clone.isCheckedFunction = a.isCheckedFunction;
if (FFlag::LuauDeprecatedAttribute)
clone.isDeprecatedFunction = a.isDeprecatedFunction;
return dest.addType(std::move(clone)); return dest.addType(std::move(clone));
} }
else if constexpr (std::is_same_v<T, TableType>) else if constexpr (std::is_same_v<T, TableType>)

View file

@ -22,7 +22,9 @@
#include <algorithm> #include <algorithm>
LUAU_FASTFLAGVARIABLE(DebugLuauSubtypingCheckPathValidity) LUAU_FASTFLAGVARIABLE(DebugLuauSubtypingCheckPathValidity)
LUAU_FASTFLAGVARIABLE(LuauSubtypingFixTailPack) LUAU_FASTFLAGVARIABLE(LuauSubtypingStopAtNormFail)
LUAU_FASTINTVARIABLE(LuauSubtypingReasoningLimit, 100)
LUAU_FASTFLAGVARIABLE(LuauSubtypingEnableReasoningLimit)
namespace Luau namespace Luau
{ {
@ -100,6 +102,9 @@ static SubtypingReasonings mergeReasonings(const SubtypingReasonings& a, const S
else else
result.insert(r); result.insert(r);
} }
if (FFlag::LuauSubtypingEnableReasoningLimit && result.size() >= size_t(FInt::LuauSubtypingReasoningLimit))
return result;
} }
for (const SubtypingReasoning& r : b) for (const SubtypingReasoning& r : b)
@ -116,6 +121,9 @@ static SubtypingReasonings mergeReasonings(const SubtypingReasonings& a, const S
else else
result.insert(r); result.insert(r);
} }
if (FFlag::LuauSubtypingEnableReasoningLimit && result.size() >= size_t(FInt::LuauSubtypingReasoningLimit))
return result;
} }
return result; return result;
@ -416,6 +424,14 @@ 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.isCacheable)
resultCache[{subTy, superTy}] = result;
return result;
}
for (const auto& [subTy, bounds] : env.mappedGenerics) for (const auto& [subTy, bounds] : env.mappedGenerics)
{ {
const auto& lb = bounds.lowerBound; const auto& lb = bounds.lowerBound;
@ -593,7 +609,12 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
if (!result.isSubtype && !result.normalizationTooComplex) if (!result.isSubtype && !result.normalizationTooComplex)
{ {
SubtypingResult semantic = isCovariantWith(env, normalizer->normalize(subTy), normalizer->normalize(superTy), scope); SubtypingResult semantic = isCovariantWith(env, normalizer->normalize(subTy), normalizer->normalize(superTy), scope);
if (semantic.isSubtype)
if (FFlag::LuauSubtypingStopAtNormFail && semantic.normalizationTooComplex)
{
result = semantic;
}
else if (semantic.isSubtype)
{ {
semantic.reasoning.clear(); semantic.reasoning.clear();
result = semantic; result = semantic;
@ -608,7 +629,12 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
if (!result.isSubtype && !result.normalizationTooComplex) if (!result.isSubtype && !result.normalizationTooComplex)
{ {
SubtypingResult semantic = isCovariantWith(env, normalizer->normalize(subTy), normalizer->normalize(superTy), scope); SubtypingResult semantic = isCovariantWith(env, normalizer->normalize(subTy), normalizer->normalize(superTy), scope);
if (semantic.isSubtype)
if (FFlag::LuauSubtypingStopAtNormFail && semantic.normalizationTooComplex)
{
result = semantic;
}
else if (semantic.isSubtype)
{ {
// Clear the semantic reasoning, as any reasonings within // Clear the semantic reasoning, as any reasonings within
// potentially contain invalid paths. // potentially contain invalid paths.
@ -754,7 +780,8 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
// Match head types pairwise // Match head types pairwise
for (size_t i = 0; i < headSize; ++i) for (size_t i = 0; i < headSize; ++i)
results.push_back(isCovariantWith(env, subHead[i], superHead[i], scope).withBothComponent(TypePath::Index{i})); results.push_back(isCovariantWith(env, subHead[i], superHead[i], scope).withBothComponent(TypePath::Index{i, TypePath::Index::Variant::Pack})
);
// Handle mismatched head sizes // Handle mismatched head sizes
@ -767,7 +794,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
for (size_t i = headSize; i < superHead.size(); ++i) for (size_t i = headSize; i < superHead.size(); ++i)
results.push_back(isCovariantWith(env, vt->ty, superHead[i], scope) results.push_back(isCovariantWith(env, vt->ty, superHead[i], scope)
.withSubPath(TypePath::PathBuilder().tail().variadic().build()) .withSubPath(TypePath::PathBuilder().tail().variadic().build())
.withSuperComponent(TypePath::Index{i})); .withSuperComponent(TypePath::Index{i, TypePath::Index::Variant::Pack}));
} }
else if (auto gt = get<GenericTypePack>(*subTail)) else if (auto gt = get<GenericTypePack>(*subTail))
{ {
@ -821,7 +848,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
{ {
for (size_t i = headSize; i < subHead.size(); ++i) for (size_t i = headSize; i < subHead.size(); ++i)
results.push_back(isCovariantWith(env, subHead[i], vt->ty, scope) results.push_back(isCovariantWith(env, subHead[i], vt->ty, scope)
.withSubComponent(TypePath::Index{i}) .withSubComponent(TypePath::Index{i, TypePath::Index::Variant::Pack})
.withSuperPath(TypePath::PathBuilder().tail().variadic().build())); .withSuperPath(TypePath::PathBuilder().tail().variadic().build()));
} }
else if (auto gt = get<GenericTypePack>(*superTail)) else if (auto gt = get<GenericTypePack>(*superTail))
@ -859,7 +886,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
else else
return SubtypingResult{false} return SubtypingResult{false}
.withSuperComponent(TypePath::PackField::Tail) .withSuperComponent(TypePath::PackField::Tail)
.withError({scope->location, UnexpectedTypePackInSubtyping{FFlag::LuauSubtypingFixTailPack ? *superTail : *subTail}}); .withError({scope->location, UnexpectedTypePackInSubtyping{*superTail}});
} }
else else
return {false}; return {false};
@ -1082,6 +1109,10 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
for (TypeId ty : superUnion) for (TypeId ty : superUnion)
{ {
SubtypingResult next = isCovariantWith(env, subTy, ty, scope); SubtypingResult next = isCovariantWith(env, subTy, ty, scope);
if (FFlag::LuauSubtypingStopAtNormFail && next.normalizationTooComplex)
return SubtypingResult{false, /* normalizationTooComplex */ true};
if (next.isSubtype) if (next.isSubtype)
return SubtypingResult{true}; return SubtypingResult{true};
} }
@ -1100,7 +1131,13 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Unio
std::vector<SubtypingResult> subtypings; std::vector<SubtypingResult> subtypings;
size_t i = 0; size_t i = 0;
for (TypeId ty : subUnion) for (TypeId ty : subUnion)
subtypings.push_back(isCovariantWith(env, ty, superTy, scope).withSubComponent(TypePath::Index{i++})); {
subtypings.push_back(isCovariantWith(env, ty, superTy, scope).withSubComponent(TypePath::Index{i++, TypePath::Index::Variant::Union}));
if (FFlag::LuauSubtypingStopAtNormFail && subtypings.back().normalizationTooComplex)
return SubtypingResult{false, /* normalizationTooComplex */ true};
}
return SubtypingResult::all(subtypings); return SubtypingResult::all(subtypings);
} }
@ -1110,7 +1147,13 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
std::vector<SubtypingResult> subtypings; std::vector<SubtypingResult> subtypings;
size_t i = 0; size_t i = 0;
for (TypeId ty : superIntersection) for (TypeId ty : superIntersection)
subtypings.push_back(isCovariantWith(env, subTy, ty, scope).withSuperComponent(TypePath::Index{i++})); {
subtypings.push_back(isCovariantWith(env, subTy, ty, scope).withSuperComponent(TypePath::Index{i++, TypePath::Index::Variant::Intersection}));
if (FFlag::LuauSubtypingStopAtNormFail && subtypings.back().normalizationTooComplex)
return SubtypingResult{false, /* normalizationTooComplex */ true};
}
return SubtypingResult::all(subtypings); return SubtypingResult::all(subtypings);
} }
@ -1120,7 +1163,13 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Inte
std::vector<SubtypingResult> subtypings; std::vector<SubtypingResult> subtypings;
size_t i = 0; size_t i = 0;
for (TypeId ty : subIntersection) for (TypeId ty : subIntersection)
subtypings.push_back(isCovariantWith(env, ty, superTy, scope).withSubComponent(TypePath::Index{i++})); {
subtypings.push_back(isCovariantWith(env, ty, superTy, scope).withSubComponent(TypePath::Index{i++, TypePath::Index::Variant::Intersection}));
if (FFlag::LuauSubtypingStopAtNormFail && subtypings.back().normalizationTooComplex)
return SubtypingResult{false, /* normalizationTooComplex */ true};
}
return SubtypingResult::any(subtypings); return SubtypingResult::any(subtypings);
} }
@ -1410,7 +1459,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Meta
// of the supertype table. // of the supertype table.
// //
// There's a flaw here in that if the __index metamethod contributes a new // There's a flaw here in that if the __index metamethod contributes a new
// field that would satisfy the subtyping relationship, we'll erronously say // field that would satisfy the subtyping relationship, we'll erroneously say
// that the metatable isn't a subtype of the table, even though they have // that the metatable isn't a subtype of the table, even though they have
// compatible properties/shapes. We'll revisit this later when we have a // compatible properties/shapes. We'll revisit this later when we have a
// better understanding of how important this is. // better understanding of how important this is.
@ -1760,7 +1809,12 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Type
{ {
results.emplace_back(); results.emplace_back();
for (TypeId superTy : superTypes) for (TypeId superTy : superTypes)
{
results.back().orElse(isCovariantWith(env, subTy, superTy, scope)); results.back().orElse(isCovariantWith(env, subTy, superTy, scope));
if (FFlag::LuauSubtypingStopAtNormFail && results.back().normalizationTooComplex)
return SubtypingResult{false, /* normalizationTooComplex */ true};
}
} }
return SubtypingResult::all(results); return SubtypingResult::all(results);

View file

@ -4,7 +4,6 @@
#include "Luau/Common.h" #include "Luau/Common.h"
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAGVARIABLE(LuauSymbolEquality)
namespace Luau namespace Luau
{ {
@ -15,10 +14,8 @@ bool Symbol::operator==(const Symbol& rhs) const
return local == rhs.local; return local == rhs.local;
else if (global.value) else if (global.value)
return rhs.global.value && global == rhs.global.value; // Subtlety: AstName::operator==(const char*) uses strcmp, not pointer identity. return rhs.global.value && global == rhs.global.value; // Subtlety: AstName::operator==(const char*) uses strcmp, not pointer identity.
else if (FFlag::LuauSolverV2 || FFlag::LuauSymbolEquality)
return !rhs.local && !rhs.global.value; // Reflexivity: we already know `this` Symbol is empty, so check that rhs is.
else else
return false; return !rhs.local && !rhs.global.value; // Reflexivity: we already know `this` Symbol is empty, so check that rhs is.
} }
std::string toString(const Symbol& name) std::string toString(const Symbol& name)

View file

@ -1,16 +1,22 @@
// 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/TableLiteralInference.h"
#include "Luau/Ast.h" #include "Luau/Ast.h"
#include "Luau/Common.h"
#include "Luau/Normalize.h" #include "Luau/Normalize.h"
#include "Luau/Simplify.h" #include "Luau/Simplify.h"
#include "Luau/Subtyping.h"
#include "Luau/Type.h" #include "Luau/Type.h"
#include "Luau/ToString.h" #include "Luau/ToString.h"
#include "Luau/TypeArena.h" #include "Luau/TypeArena.h"
#include "Luau/TypeUtils.h" #include "Luau/TypeUtils.h"
#include "Luau/Unifier2.h" #include "Luau/Unifier2.h"
LUAU_FASTFLAGVARIABLE(LuauDontInPlaceMutateTableType) LUAU_FASTFLAGVARIABLE(LuauBidirectionalInferenceUpcast)
LUAU_FASTFLAGVARIABLE(LuauAllowNonSharedTableTypesInLiteral) LUAU_FASTFLAGVARIABLE(LuauBidirectionalInferenceCollectIndexerTypes)
LUAU_FASTFLAGVARIABLE(LuauBidirectionalFailsafe)
LUAU_FASTFLAGVARIABLE(LuauBidirectionalInferenceElideAssert)
namespace Luau namespace Luau
{ {
@ -112,6 +118,7 @@ TypeId matchLiteralType(
NotNull<BuiltinTypes> builtinTypes, NotNull<BuiltinTypes> builtinTypes,
NotNull<TypeArena> arena, NotNull<TypeArena> arena,
NotNull<Unifier2> unifier, NotNull<Unifier2> unifier,
NotNull<Subtyping> subtyping,
TypeId expectedType, TypeId expectedType,
TypeId exprType, TypeId exprType,
const AstExpr* expr, const AstExpr* expr,
@ -132,17 +139,38 @@ TypeId matchLiteralType(
* things like replace explicit named properties with indexers as required * things like replace explicit named properties with indexers as required
* by the expected type. * by the expected type.
*/ */
if (!isLiteral(expr)) if (!isLiteral(expr))
return exprType; {
if (FFlag::LuauBidirectionalInferenceUpcast)
{
auto result = subtyping->isSubtype(/*subTy=*/exprType, /*superTy=*/expectedType, unifier->scope);
return result.isSubtype ? expectedType : exprType;
}
else
return exprType;
}
expectedType = follow(expectedType); expectedType = follow(expectedType);
exprType = follow(exprType); exprType = follow(exprType);
if (get<AnyType>(expectedType) || get<UnknownType>(expectedType)) if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes)
{ {
// "Narrowing" to unknown or any is not going to do anything useful. // The intent of `matchLiteralType` is to upcast values when it's safe
return exprType; // to do so. it's always safe to upcast to `any` or `unknown`, so we
// can unconditionally do so here.
if (is<AnyType, UnknownType>(expectedType))
return expectedType;
} }
else
{
if (get<AnyType>(expectedType) || get<UnknownType>(expectedType))
{
// "Narrowing" to unknown or any is not going to do anything useful.
return exprType;
}
}
if (expr->is<AstExprConstantString>()) if (expr->is<AstExprConstantString>())
{ {
@ -210,11 +238,27 @@ TypeId matchLiteralType(
return exprType; return exprType;
} }
// TODO: lambdas
if (FFlag::LuauBidirectionalInferenceUpcast && expr->is<AstExprFunction>())
{
// TODO: Push argument / return types into the lambda. For now, just do
// the non-literal thing: check for a subtype and upcast if valid.
auto result = subtyping->isSubtype(/*subTy=*/exprType, /*superTy=*/expectedType, unifier->scope);
return result.isSubtype ? expectedType : exprType;
}
if (auto exprTable = expr->as<AstExprTable>()) if (auto exprTable = expr->as<AstExprTable>())
{ {
TableType* const tableTy = getMutable<TableType>(exprType); TableType* const tableTy = getMutable<TableType>(exprType);
// This can occur if we have an expression like:
//
// { x = {}, x = 42 }
//
// The type of this will be `{ x: number }`
if (FFlag::LuauBidirectionalFailsafe && !tableTy)
return exprType;
LUAU_ASSERT(tableTy); LUAU_ASSERT(tableTy);
const TableType* expectedTableTy = get<TableType>(expectedType); const TableType* expectedTableTy = get<TableType>(expectedType);
@ -229,7 +273,7 @@ TypeId matchLiteralType(
if (tt) if (tt)
{ {
TypeId res = matchLiteralType(astTypes, astExpectedTypes, builtinTypes, arena, unifier, *tt, exprType, expr, toBlock); TypeId res = matchLiteralType(astTypes, astExpectedTypes, builtinTypes, arena, unifier, subtyping, *tt, exprType, expr, toBlock);
parts.push_back(res); parts.push_back(res);
return arena->addType(UnionType{std::move(parts)}); return arena->addType(UnionType{std::move(parts)});
@ -241,6 +285,9 @@ TypeId matchLiteralType(
DenseHashSet<AstExprConstantString*> keysToDelete{nullptr}; DenseHashSet<AstExprConstantString*> keysToDelete{nullptr};
DenseHashSet<TypeId> indexerKeyTypes{nullptr};
DenseHashSet<TypeId> indexerValueTypes{nullptr};
for (const AstExprTable::Item& item : exprTable->items) for (const AstExprTable::Item& item : exprTable->items)
{ {
if (isRecord(item)) if (isRecord(item))
@ -248,23 +295,20 @@ TypeId matchLiteralType(
const AstArray<char>& s = item.key->as<AstExprConstantString>()->value; const AstArray<char>& s = item.key->as<AstExprConstantString>()->value;
std::string keyStr{s.data, s.data + s.size}; std::string keyStr{s.data, s.data + s.size};
auto it = tableTy->props.find(keyStr); auto it = tableTy->props.find(keyStr);
// This can occur, potentially, if we are re-entrant.
if (FFlag::LuauBidirectionalFailsafe && it == tableTy->props.end())
continue;
LUAU_ASSERT(it != tableTy->props.end()); LUAU_ASSERT(it != tableTy->props.end());
Property& prop = it->second; Property& prop = it->second;
if (FFlag::LuauAllowNonSharedTableTypesInLiteral) // If we encounter a duplcate property, we may have already
{ // set it to be read-only. If that's the case, the only thing
// If we encounter a duplcate property, we may have already // that will definitely crash is trying to access a write
// set it to be read-only. If that's the case, the only thing // only property.
// that will definitely crash is trying to access a write LUAU_ASSERT(!prop.isWriteOnly());
// only property.
LUAU_ASSERT(!prop.isWriteOnly());
}
else
{
// Table literals always initially result in shared read-write types
LUAU_ASSERT(prop.isShared());
}
TypeId propTy = *prop.readTy; TypeId propTy = *prop.readTy;
auto it2 = expectedTableTy->props.find(keyStr); auto it2 = expectedTableTy->props.find(keyStr);
@ -285,21 +329,27 @@ TypeId matchLiteralType(
builtinTypes, builtinTypes,
arena, arena,
unifier, unifier,
subtyping,
expectedTableTy->indexer->indexResultType, expectedTableTy->indexer->indexResultType,
propTy, propTy,
item.value, item.value,
toBlock toBlock
); );
if (tableTy->indexer) if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes)
unifier->unify(matchedType, tableTy->indexer->indexResultType); {
indexerKeyTypes.insert(arena->addType(SingletonType{StringSingleton{keyStr}}));
indexerValueTypes.insert(matchedType);
}
else else
tableTy->indexer = TableIndexer{expectedTableTy->indexer->indexType, matchedType}; {
if (tableTy->indexer)
unifier->unify(matchedType, tableTy->indexer->indexResultType);
else
tableTy->indexer = TableIndexer{expectedTableTy->indexer->indexType, matchedType};
}
if (FFlag::LuauDontInPlaceMutateTableType) keysToDelete.insert(item.key->as<AstExprConstantString>());
keysToDelete.insert(item.key->as<AstExprConstantString>());
else
tableTy->props.erase(keyStr);
} }
// If it's just an extra property and the expected type // If it's just an extra property and the expected type
@ -322,22 +372,25 @@ TypeId matchLiteralType(
// quadratic in a hurry. // quadratic in a hurry.
if (expectedProp.isShared()) if (expectedProp.isShared())
{ {
matchedType = matchedType = matchLiteralType(
matchLiteralType(astTypes, astExpectedTypes, builtinTypes, arena, unifier, *expectedReadTy, propTy, item.value, toBlock); astTypes, astExpectedTypes, builtinTypes, arena, unifier, subtyping, *expectedReadTy, propTy, item.value, toBlock
);
prop.readTy = matchedType; prop.readTy = matchedType;
prop.writeTy = matchedType; prop.writeTy = matchedType;
} }
else if (expectedReadTy) else if (expectedReadTy)
{ {
matchedType = matchedType = matchLiteralType(
matchLiteralType(astTypes, astExpectedTypes, builtinTypes, arena, unifier, *expectedReadTy, propTy, item.value, toBlock); astTypes, astExpectedTypes, builtinTypes, arena, unifier, subtyping, *expectedReadTy, propTy, item.value, toBlock
);
prop.readTy = matchedType; prop.readTy = matchedType;
prop.writeTy.reset(); prop.writeTy.reset();
} }
else if (expectedWriteTy) else if (expectedWriteTy)
{ {
matchedType = matchedType = matchLiteralType(
matchLiteralType(astTypes, astExpectedTypes, builtinTypes, arena, unifier, *expectedWriteTy, propTy, item.value, toBlock); astTypes, astExpectedTypes, builtinTypes, arena, unifier, subtyping, *expectedWriteTy, propTy, item.value, toBlock
);
prop.readTy.reset(); prop.readTy.reset();
prop.writeTy = matchedType; prop.writeTy = matchedType;
} }
@ -354,10 +407,16 @@ TypeId matchLiteralType(
LUAU_ASSERT(matchedType); LUAU_ASSERT(matchedType);
(*astExpectedTypes)[item.value] = matchedType; (*astExpectedTypes)[item.value] = matchedType;
// NOTE: We do *not* add to the potential indexer types here.
// I think this is correct to support something like:
//
// { [string]: number, foo: boolean }
//
} }
else if (item.kind == AstExprTable::Item::List) else if (item.kind == AstExprTable::Item::List)
{ {
LUAU_ASSERT(tableTy->indexer); if (!FFlag::LuauBidirectionalInferenceCollectIndexerTypes || !FFlag::LuauBidirectionalInferenceElideAssert)
LUAU_ASSERT(tableTy->indexer);
if (expectedTableTy->indexer) if (expectedTableTy->indexer)
{ {
@ -371,15 +430,24 @@ TypeId matchLiteralType(
builtinTypes, builtinTypes,
arena, arena,
unifier, unifier,
subtyping,
expectedTableTy->indexer->indexResultType, expectedTableTy->indexer->indexResultType,
*propTy, *propTy,
item.value, item.value,
toBlock toBlock
); );
// if the index result type is the prop type, we can replace it with the matched type here. if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes)
if (tableTy->indexer->indexResultType == *propTy) {
tableTy->indexer->indexResultType = matchedType; indexerKeyTypes.insert(builtinTypes->numberType);
indexerValueTypes.insert(matchedType);
}
else
{
// if the index result type is the prop type, we can replace it with the matched type here.
if (tableTy->indexer->indexResultType == *propTy)
tableTy->indexer->indexResultType = matchedType;
}
} }
} }
else if (item.kind == AstExprTable::Item::General) else if (item.kind == AstExprTable::Item::General)
@ -401,19 +469,22 @@ TypeId matchLiteralType(
// Populate expected types for non-string keys declared with [] (the code below will handle the case where they are strings) // Populate expected types for non-string keys declared with [] (the code below will handle the case where they are strings)
if (!item.key->as<AstExprConstantString>() && expectedTableTy->indexer) if (!item.key->as<AstExprConstantString>() && expectedTableTy->indexer)
(*astExpectedTypes)[item.key] = expectedTableTy->indexer->indexType; (*astExpectedTypes)[item.key] = expectedTableTy->indexer->indexType;
if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes)
{
indexerKeyTypes.insert(tKey);
indexerValueTypes.insert(tProp);
}
} }
else else
LUAU_ASSERT(!"Unexpected"); LUAU_ASSERT(!"Unexpected");
} }
if (FFlag::LuauDontInPlaceMutateTableType) for (const auto& key : keysToDelete)
{ {
for (const auto& key : keysToDelete) const AstArray<char>& s = key->value;
{ std::string keyStr{s.data, s.data + s.size};
const AstArray<char>& s = key->value; tableTy->props.erase(keyStr);
std::string keyStr{s.data, s.data + s.size};
tableTy->props.erase(keyStr);
}
} }
// Keys that the expectedType says we should have, but that aren't // Keys that the expectedType says we should have, but that aren't
@ -465,9 +536,39 @@ TypeId matchLiteralType(
// have one too. // have one too.
// TODO: If the expected table also has an indexer, we might want to // TODO: If the expected table also has an indexer, we might want to
// push the expected indexer's types into it. // push the expected indexer's types into it.
if (expectedTableTy->indexer && !tableTy->indexer) if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes && expectedTableTy->indexer)
{ {
tableTy->indexer = expectedTableTy->indexer; if (indexerValueTypes.size() > 0 && indexerKeyTypes.size() > 0)
{
TypeId inferredKeyType = builtinTypes->neverType;
TypeId inferredValueType = builtinTypes->neverType;
for (auto kt : indexerKeyTypes)
{
auto simplified = simplifyUnion(builtinTypes, arena, inferredKeyType, kt);
inferredKeyType = simplified.result;
}
for (auto vt : indexerValueTypes)
{
auto simplified = simplifyUnion(builtinTypes, arena, inferredValueType, vt);
inferredValueType = simplified.result;
}
tableTy->indexer = TableIndexer{inferredKeyType, inferredValueType};
auto keyCheck = subtyping->isSubtype(inferredKeyType, expectedTableTy->indexer->indexType, unifier->scope);
if (keyCheck.isSubtype)
tableTy->indexer->indexType = expectedTableTy->indexer->indexType;
auto valueCheck = subtyping->isSubtype(inferredValueType, expectedTableTy->indexer->indexResultType, unifier->scope);
if (valueCheck.isSubtype)
tableTy->indexer->indexResultType = expectedTableTy->indexer->indexResultType;
}
else
LUAU_ASSERT(indexerKeyTypes.empty() && indexerValueTypes.empty());
}
else
{
if (expectedTableTy->indexer && !tableTy->indexer)
{
tableTy->indexer = expectedTableTy->indexer;
}
} }
} }

View file

@ -301,6 +301,28 @@ struct StringifierState
emit(std::to_string(i).c_str()); emit(std::to_string(i).c_str());
} }
void emit(Polarity p)
{
switch (p)
{
case Polarity::None:
emit(" ");
break;
case Polarity::Negative:
emit(" -");
break;
case Polarity::Positive:
emit("+ ");
break;
case Polarity::Mixed:
emit("+-");
break;
default:
emit("!!");
break;
}
}
void indent() void indent()
{ {
indentation += 4; indentation += 4;
@ -482,6 +504,8 @@ struct TypeStringifier
{ {
state.emit("'"); state.emit("'");
state.emit(state.getName(ty)); state.emit(state.getName(ty));
if (FInt::DebugLuauVerboseTypeNames >= 1)
state.emit(ftv.polarity);
} }
else else
{ {
@ -494,6 +518,9 @@ struct TypeStringifier
state.emit("'"); state.emit("'");
state.emit(state.getName(ty)); state.emit(state.getName(ty));
if (FInt::DebugLuauVerboseTypeNames >= 1)
state.emit(ftv.polarity);
if (!get<UnknownType>(upperBound)) if (!get<UnknownType>(upperBound))
{ {
state.emit(" <: "); state.emit(" <: ");
@ -509,6 +536,9 @@ struct TypeStringifier
state.emit(state.getName(ty)); state.emit(state.getName(ty));
if (FFlag::LuauSolverV2 && FInt::DebugLuauVerboseTypeNames >= 1)
state.emit(ftv.polarity);
if (FInt::DebugLuauVerboseTypeNames >= 2) if (FInt::DebugLuauVerboseTypeNames >= 2)
{ {
state.emit("-"); state.emit("-");
@ -538,6 +568,9 @@ struct TypeStringifier
else else
state.emit(state.getName(ty)); state.emit(state.getName(ty));
if (FInt::DebugLuauVerboseTypeNames >= 1)
state.emit(gtv.polarity);
if (FInt::DebugLuauVerboseTypeNames >= 2) if (FInt::DebugLuauVerboseTypeNames >= 2)
{ {
state.emit("-"); state.emit("-");
@ -1222,6 +1255,9 @@ struct TypePackStringifier
state.emit(state.getName(tp)); state.emit(state.getName(tp));
} }
if (FInt::DebugLuauVerboseTypeNames >= 1)
state.emit(pack.polarity);
if (FInt::DebugLuauVerboseTypeNames >= 2) if (FInt::DebugLuauVerboseTypeNames >= 2)
{ {
state.emit("-"); state.emit("-");
@ -1241,6 +1277,9 @@ struct TypePackStringifier
state.emit("free-"); state.emit("free-");
state.emit(state.getName(tp)); state.emit(state.getName(tp));
if (FInt::DebugLuauVerboseTypeNames >= 1)
state.emit(pack.polarity);
if (FInt::DebugLuauVerboseTypeNames >= 2) if (FInt::DebugLuauVerboseTypeNames >= 2)
{ {
state.emit("-"); state.emit("-");

View file

@ -10,10 +10,11 @@
#include <limits> #include <limits>
#include <math.h> #include <math.h>
LUAU_FASTFLAG(LuauStoreCSTData) LUAU_FASTFLAG(LuauStoreCSTData2)
LUAU_FASTFLAG(LuauExtendStatEndPosWithSemicolon) LUAU_FASTFLAG(LuauAstTypeGroup3)
LUAU_FASTFLAG(LuauAstTypeGroup2)
LUAU_FASTFLAG(LuauFixDoBlockEndLocation) LUAU_FASTFLAG(LuauFixDoBlockEndLocation)
LUAU_FASTFLAG(LuauParseOptionalAsNode2)
LUAU_FASTFLAG(LuauFixFunctionWithAttributesStartLocation)
namespace namespace
{ {
@ -166,7 +167,7 @@ struct StringWriter : Writer
void symbol(std::string_view s) override void symbol(std::string_view s) override
{ {
if (FFlag::LuauStoreCSTData) if (FFlag::LuauStoreCSTData2)
{ {
write(s); write(s);
} }
@ -256,7 +257,7 @@ public:
first = !first; first = !first;
else else
{ {
if (FFlag::LuauStoreCSTData && commaPosition) if (FFlag::LuauStoreCSTData2 && commaPosition)
{ {
writer.advance(*commaPosition); writer.advance(*commaPosition);
commaPosition++; commaPosition++;
@ -271,6 +272,43 @@ private:
const Position* commaPosition; const Position* commaPosition;
}; };
class ArgNameInserter
{
public:
ArgNameInserter(Writer& w, AstArray<std::optional<AstArgumentName>> names, AstArray<std::optional<Position>> colonPositions)
: writer(w)
, names(names)
, colonPositions(colonPositions)
{
}
void operator()()
{
if (idx < names.size)
{
const auto name = names.data[idx];
if (name.has_value())
{
writer.advance(name->second.begin);
writer.identifier(name->first.value);
if (idx < colonPositions.size)
{
LUAU_ASSERT(colonPositions.data[idx].has_value());
writer.advance(*colonPositions.data[idx]);
}
writer.symbol(":");
}
}
idx++;
}
private:
Writer& writer;
AstArray<std::optional<AstArgumentName>> names;
AstArray<std::optional<Position>> colonPositions;
size_t idx = 0;
};
struct Printer_DEPRECATED struct Printer_DEPRECATED
{ {
explicit Printer_DEPRECATED(Writer& writer) explicit Printer_DEPRECATED(Writer& writer)
@ -330,7 +368,7 @@ struct Printer_DEPRECATED
else if (typeCount == 1) else if (typeCount == 1)
{ {
bool shouldParenthesize = unconditionallyParenthesize && (list.types.size == 0 || !list.types.data[0]->is<AstTypeGroup>()); bool shouldParenthesize = unconditionallyParenthesize && (list.types.size == 0 || !list.types.data[0]->is<AstTypeGroup>());
if (FFlag::LuauAstTypeGroup2 ? shouldParenthesize : unconditionallyParenthesize) if (FFlag::LuauAstTypeGroup3 ? shouldParenthesize : unconditionallyParenthesize)
writer.symbol("("); writer.symbol("(");
// Only variadic tail // Only variadic tail
@ -343,7 +381,7 @@ struct Printer_DEPRECATED
visualizeTypeAnnotation(*list.types.data[0]); visualizeTypeAnnotation(*list.types.data[0]);
} }
if (FFlag::LuauAstTypeGroup2 ? shouldParenthesize : unconditionallyParenthesize) if (FFlag::LuauAstTypeGroup3 ? shouldParenthesize : unconditionallyParenthesize)
writer.symbol(")"); writer.symbol(")");
} }
else else
@ -1191,9 +1229,18 @@ struct Printer_DEPRECATED
AstType* l = a->types.data[0]; AstType* l = a->types.data[0];
AstType* r = a->types.data[1]; AstType* r = a->types.data[1];
auto lta = l->as<AstTypeReference>(); if (FFlag::LuauParseOptionalAsNode2)
if (lta && lta->name == "nil") {
std::swap(l, r); auto lta = l->as<AstTypeReference>();
if (lta && lta->name == "nil" && !r->is<AstTypeOptional>())
std::swap(l, r);
}
else
{
auto lta = l->as<AstTypeReference>();
if (lta && lta->name == "nil")
std::swap(l, r);
}
// it's still possible that we had a (T | U) or (T | nil) and not (nil | T) // it's still possible that we had a (T | U) or (T | nil) and not (nil | T)
auto rta = r->as<AstTypeReference>(); auto rta = r->as<AstTypeReference>();
@ -1216,6 +1263,15 @@ struct Printer_DEPRECATED
for (size_t i = 0; i < a->types.size; ++i) for (size_t i = 0; i < a->types.size; ++i)
{ {
if (FFlag::LuauParseOptionalAsNode2)
{
if (a->types.data[i]->is<AstTypeOptional>())
{
writer.symbol("?");
continue;
}
}
if (i > 0) if (i > 0)
{ {
writer.maybeSpace(a->types.data[i]->location.begin, 2); writer.maybeSpace(a->types.data[i]->location.begin, 2);
@ -1312,7 +1368,7 @@ struct Printer
} }
} }
void visualizeTypePackAnnotation(const AstTypePack& annotation, bool forVarArg) void visualizeTypePackAnnotation(AstTypePack& annotation, bool forVarArg)
{ {
advance(annotation.location.begin); advance(annotation.location.begin);
if (const AstTypePackVariadic* variadicTp = annotation.as<AstTypePackVariadic>()) if (const AstTypePackVariadic* variadicTp = annotation.as<AstTypePackVariadic>())
@ -1322,15 +1378,22 @@ struct Printer
visualizeTypeAnnotation(*variadicTp->variadicType); visualizeTypeAnnotation(*variadicTp->variadicType);
} }
else if (const AstTypePackGeneric* genericTp = annotation.as<AstTypePackGeneric>()) else if (AstTypePackGeneric* genericTp = annotation.as<AstTypePackGeneric>())
{ {
writer.symbol(genericTp->genericName.value); writer.symbol(genericTp->genericName.value);
if (const auto cstNode = lookupCstNode<CstTypePackGeneric>(genericTp))
advance(cstNode->ellipsisPosition);
writer.symbol("..."); writer.symbol("...");
} }
else if (const AstTypePackExplicit* explicitTp = annotation.as<AstTypePackExplicit>()) else if (AstTypePackExplicit* explicitTp = annotation.as<AstTypePackExplicit>())
{ {
LUAU_ASSERT(!forVarArg); LUAU_ASSERT(!forVarArg);
visualizeTypeList(explicitTp->typeList, true); if (const auto cstNode = lookupCstNode<CstTypePackExplicit>(explicitTp))
visualizeTypeList(
explicitTp->typeList, true, cstNode->openParenthesesPosition, cstNode->closeParenthesesPosition, cstNode->commaPositions
);
else
visualizeTypeList(explicitTp->typeList, true);
} }
else else
{ {
@ -1338,19 +1401,37 @@ struct Printer
} }
} }
void visualizeTypeList(const AstTypeList& list, bool unconditionallyParenthesize) void visualizeNamedTypeList(
const AstTypeList& list,
bool unconditionallyParenthesize,
std::optional<Position> openParenthesesPosition,
std::optional<Position> closeParenthesesPosition,
AstArray<Position> commaPositions,
AstArray<std::optional<AstArgumentName>> argNames,
AstArray<std::optional<Position>> argNamesColonPositions
)
{ {
size_t typeCount = list.types.size + (list.tailType != nullptr ? 1 : 0); size_t typeCount = list.types.size + (list.tailType != nullptr ? 1 : 0);
if (typeCount == 0) if (typeCount == 0)
{ {
if (openParenthesesPosition)
advance(*openParenthesesPosition);
writer.symbol("("); writer.symbol("(");
if (closeParenthesesPosition)
advance(*closeParenthesesPosition);
writer.symbol(")"); writer.symbol(")");
} }
else if (typeCount == 1) else if (typeCount == 1)
{ {
bool shouldParenthesize = unconditionallyParenthesize && (list.types.size == 0 || !list.types.data[0]->is<AstTypeGroup>()); bool shouldParenthesize = unconditionallyParenthesize && (list.types.size == 0 || !list.types.data[0]->is<AstTypeGroup>());
if (FFlag::LuauAstTypeGroup2 ? shouldParenthesize : unconditionallyParenthesize) if (FFlag::LuauAstTypeGroup3 ? shouldParenthesize : unconditionallyParenthesize)
{
if (openParenthesesPosition)
advance(*openParenthesesPosition);
writer.symbol("("); writer.symbol("(");
}
ArgNameInserter(writer, argNames, argNamesColonPositions)();
// Only variadic tail // Only variadic tail
if (list.types.size == 0) if (list.types.size == 0)
@ -1362,34 +1443,51 @@ struct Printer
visualizeTypeAnnotation(*list.types.data[0]); visualizeTypeAnnotation(*list.types.data[0]);
} }
if (FFlag::LuauAstTypeGroup2 ? shouldParenthesize : unconditionallyParenthesize) if (FFlag::LuauAstTypeGroup3 ? shouldParenthesize : unconditionallyParenthesize)
{
if (closeParenthesesPosition)
advance(*closeParenthesesPosition);
writer.symbol(")"); writer.symbol(")");
}
} }
else else
{ {
if (openParenthesesPosition)
advance(*openParenthesesPosition);
writer.symbol("("); writer.symbol("(");
bool first = true; CommaSeparatorInserter comma(writer, commaPositions.size > 0 ? commaPositions.begin() : nullptr);
ArgNameInserter argName(writer, argNames, argNamesColonPositions);
for (const auto& el : list.types) for (const auto& el : list.types)
{ {
if (first) comma();
first = false; argName();
else
writer.symbol(",");
visualizeTypeAnnotation(*el); visualizeTypeAnnotation(*el);
} }
if (list.tailType) if (list.tailType)
{ {
writer.symbol(","); comma();
visualizeTypePackAnnotation(*list.tailType, false); visualizeTypePackAnnotation(*list.tailType, false);
} }
if (closeParenthesesPosition)
advance(*closeParenthesesPosition);
writer.symbol(")"); writer.symbol(")");
} }
} }
void visualizeTypeList(
const AstTypeList& list,
bool unconditionallyParenthesize,
std::optional<Position> openParenthesesPosition = std::nullopt,
std::optional<Position> closeParenthesesPosition = std::nullopt,
AstArray<Position> commaPositions = {}
)
{
visualizeNamedTypeList(list, unconditionallyParenthesize, openParenthesesPosition, closeParenthesesPosition, commaPositions, {}, {});
}
bool isIntegerish(double d) bool isIntegerish(double d)
{ {
if (d <= std::numeric_limits<int>::max() && d >= std::numeric_limits<int>::min()) if (d <= std::numeric_limits<int>::max() && d >= std::numeric_limits<int>::min())
@ -1400,13 +1498,14 @@ struct Printer
void visualize(AstExpr& expr) void visualize(AstExpr& expr)
{ {
advance(expr.location.begin); if (!expr.is<AstExprFunction>() || FFlag::LuauFixFunctionWithAttributesStartLocation)
advance(expr.location.begin);
if (const auto& a = expr.as<AstExprGroup>()) if (const auto& a = expr.as<AstExprGroup>())
{ {
writer.symbol("("); writer.symbol("(");
visualize(*a->expr); visualize(*a->expr);
advance(Position{a->location.end.line, a->location.end.column - 1}); advanceBefore(a->location.end, 1);
writer.symbol(")"); writer.symbol(")");
} }
else if (expr.is<AstExprConstantNil>()) else if (expr.is<AstExprConstantNil>())
@ -1534,6 +1633,17 @@ struct Printer
} }
else if (const auto& a = expr.as<AstExprFunction>()) else if (const auto& a = expr.as<AstExprFunction>())
{ {
for (const auto& attribute : a->attributes)
visualizeAttribute(*attribute);
if (FFlag::LuauFixFunctionWithAttributesStartLocation)
{
if (const auto cstNode = lookupCstNode<CstExprFunction>(a))
advance(cstNode->functionKeywordPosition);
}
else
{
advance(a->location.begin);
}
writer.keyword("function"); writer.keyword("function");
visualizeFunctionBody(*a); visualizeFunctionBody(*a);
} }
@ -1775,9 +1885,18 @@ struct Printer
writer.advance(newPos); writer.advance(newPos);
} }
void advanceBefore(const Position& newPos, unsigned int tokenLength)
{
if (newPos.column >= tokenLength)
advance(Position{newPos.line, newPos.column - tokenLength});
else
advance(newPos);
}
void visualize(AstStat& program) void visualize(AstStat& program)
{ {
advance(program.location.begin); if ((!program.is<AstStatLocalFunction>() && !program.is<AstStatFunction>()) || FFlag::LuauFixFunctionWithAttributesStartLocation)
advance(program.location.begin);
if (const auto& block = program.as<AstStatBlock>()) if (const auto& block = program.as<AstStatBlock>())
{ {
@ -1817,8 +1936,8 @@ struct Printer
visualizeBlock(*a->body); visualizeBlock(*a->body);
if (const auto cstNode = lookupCstNode<CstStatRepeat>(a)) if (const auto cstNode = lookupCstNode<CstStatRepeat>(a))
writer.advance(cstNode->untilPosition); writer.advance(cstNode->untilPosition);
else if (a->condition->location.begin.column > 5) else
writer.advance(Position{a->condition->location.begin.line, a->condition->location.begin.column - 6}); advanceBefore(a->condition->location.begin, 6);
writer.keyword("until"); writer.keyword("until");
visualize(*a->condition); visualize(*a->condition);
} }
@ -2014,13 +2133,36 @@ struct Printer
} }
else if (const auto& a = program.as<AstStatFunction>()) else if (const auto& a = program.as<AstStatFunction>())
{ {
for (const auto& attribute : a->func->attributes)
visualizeAttribute(*attribute);
if (FFlag::LuauFixFunctionWithAttributesStartLocation)
{
if (const auto cstNode = lookupCstNode<CstStatFunction>(a))
advance(cstNode->functionKeywordPosition);
}
else
{
advance(a->location.begin);
}
writer.keyword("function"); writer.keyword("function");
visualize(*a->name); visualize(*a->name);
visualizeFunctionBody(*a->func); visualizeFunctionBody(*a->func);
} }
else if (const auto& a = program.as<AstStatLocalFunction>()) else if (const auto& a = program.as<AstStatLocalFunction>())
{ {
for (const auto& attribute : a->func->attributes)
visualizeAttribute(*attribute);
const auto cstNode = lookupCstNode<CstStatLocalFunction>(a); const auto cstNode = lookupCstNode<CstStatLocalFunction>(a);
if (FFlag::LuauFixFunctionWithAttributesStartLocation)
{
if (cstNode)
advance(cstNode->localKeywordPosition);
}
else
{
advance(a->location.begin);
}
writer.keyword("local"); writer.keyword("local");
@ -2121,7 +2263,20 @@ struct Printer
{ {
if (writeTypes) if (writeTypes)
{ {
writer.keyword("type function"); const auto cstNode = lookupCstNode<CstStatTypeFunction>(t);
if (t->exported)
writer.keyword("export");
if (cstNode)
advance(cstNode->typeKeywordPosition);
else
writer.space();
writer.keyword("type");
if (cstNode)
advance(cstNode->functionKeywordPosition);
else
writer.space();
writer.keyword("function");
advance(t->nameLocation.begin);
writer.identifier(t->name.value); writer.identifier(t->name.value);
visualizeFunctionBody(*t->body); visualizeFunctionBody(*t->body);
} }
@ -2151,17 +2306,23 @@ struct Printer
if (program.hasSemicolon) if (program.hasSemicolon)
{ {
if (FFlag::LuauStoreCSTData) if (FFlag::LuauStoreCSTData2)
advance(Position{program.location.end.line, program.location.end.column - 1}); advanceBefore(program.location.end, 1);
writer.symbol(";"); writer.symbol(";");
} }
} }
void visualizeFunctionBody(AstExprFunction& func) void visualizeFunctionBody(AstExprFunction& 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); CommaSeparatorInserter comma(writer, cstNode ? cstNode->genericsCommaPositions.begin() : nullptr);
if (cstNode)
advance(cstNode->openGenericsPosition);
writer.symbol("<"); writer.symbol("<");
for (const auto& o : func.generics) for (const auto& o : func.generics)
{ {
@ -2176,13 +2337,19 @@ struct Printer
writer.advance(o->location.begin); writer.advance(o->location.begin);
writer.identifier(o->name.value); writer.identifier(o->name.value);
if (const auto* genericTypePackCstNode = lookupCstNode<CstGenericTypePack>(o))
advance(genericTypePackCstNode->ellipsisPosition);
writer.symbol("..."); writer.symbol("...");
} }
if (cstNode)
advance(cstNode->closeGenericsPosition);
writer.symbol(">"); writer.symbol(">");
} }
if (func.argLocation)
advance(func.argLocation->begin);
writer.symbol("("); writer.symbol("(");
CommaSeparatorInserter comma(writer); CommaSeparatorInserter comma(writer, cstNode ? cstNode->argsCommaPositions.begin() : nullptr);
for (size_t i = 0; i < func.args.size; ++i) for (size_t i = 0; i < func.args.size; ++i)
{ {
@ -2212,10 +2379,14 @@ struct Printer
} }
} }
if (func.argLocation)
advanceBefore(func.argLocation->end, 1);
writer.symbol(")"); writer.symbol(")");
if (writeTypes && func.returnAnnotation) if (writeTypes && func.returnAnnotation)
{ {
if (cstNode)
advance(cstNode->returnSpecifierPosition);
writer.symbol(":"); writer.symbol(":");
writer.space(); writer.space();
@ -2301,6 +2472,23 @@ struct Printer
} }
} }
void visualizeAttribute(AstAttr& attribute)
{
advance(attribute.location.begin);
switch (attribute.type)
{
case AstAttr::Checked:
writer.keyword("@checked");
break;
case AstAttr::Native:
writer.keyword("@native");
break;
case AstAttr::Deprecated:
writer.keyword("@deprecated");
break;
}
}
void visualizeTypeAnnotation(AstType& typeAnnotation) void visualizeTypeAnnotation(AstType& typeAnnotation)
{ {
advance(typeAnnotation.location.begin); advance(typeAnnotation.location.begin);
@ -2340,9 +2528,13 @@ struct Printer
} }
else if (const auto& a = typeAnnotation.as<AstTypeFunction>()) else if (const auto& a = typeAnnotation.as<AstTypeFunction>())
{ {
const auto cstNode = lookupCstNode<CstTypeFunction>(a);
if (a->generics.size > 0 || a->genericPacks.size > 0) if (a->generics.size > 0 || a->genericPacks.size > 0)
{ {
CommaSeparatorInserter comma(writer); CommaSeparatorInserter comma(writer, cstNode ? cstNode->genericsCommaPositions.begin() : nullptr);
if (cstNode)
advance(cstNode->openGenericsPosition);
writer.symbol("<"); writer.symbol("<");
for (const auto& o : a->generics) for (const auto& o : a->generics)
{ {
@ -2357,15 +2549,29 @@ struct Printer
writer.advance(o->location.begin); writer.advance(o->location.begin);
writer.identifier(o->name.value); writer.identifier(o->name.value);
if (const auto* genericTypePackCstNode = lookupCstNode<CstGenericTypePack>(o))
advance(genericTypePackCstNode->ellipsisPosition);
writer.symbol("..."); writer.symbol("...");
} }
if (cstNode)
advance(cstNode->closeGenericsPosition);
writer.symbol(">"); writer.symbol(">");
} }
{ {
visualizeTypeList(a->argTypes, true); visualizeNamedTypeList(
a->argTypes,
true,
cstNode ? std::make_optional(cstNode->openArgsPosition) : std::nullopt,
cstNode ? std::make_optional(cstNode->closeArgsPosition) : std::nullopt,
cstNode ? cstNode->argumentsCommaPositions : Luau::AstArray<Position>{},
a->argNames,
cstNode ? cstNode->argumentNameColonPositions : Luau::AstArray<std::optional<Position>>{}
);
} }
if (cstNode)
advance(cstNode->returnArrowPosition);
writer.symbol("->"); writer.symbol("->");
visualizeTypeList(a->returnTypes, true); visualizeTypeList(a->returnTypes, true);
} }
@ -2527,14 +2733,25 @@ struct Printer
} }
else if (const auto& a = typeAnnotation.as<AstTypeUnion>()) else if (const auto& a = typeAnnotation.as<AstTypeUnion>())
{ {
if (a->types.size == 2) const auto cstNode = lookupCstNode<CstTypeUnion>(a);
if (!cstNode && a->types.size == 2)
{ {
AstType* l = a->types.data[0]; AstType* l = a->types.data[0];
AstType* r = a->types.data[1]; AstType* r = a->types.data[1];
auto lta = l->as<AstTypeReference>(); if (FFlag::LuauParseOptionalAsNode2)
if (lta && lta->name == "nil") {
std::swap(l, r); auto lta = l->as<AstTypeReference>();
if (lta && lta->name == "nil" && !r->is<AstTypeOptional>())
std::swap(l, r);
}
else
{
auto lta = l->as<AstTypeReference>();
if (lta && lta->name == "nil")
std::swap(l, r);
}
// it's still possible that we had a (T | U) or (T | nil) and not (nil | T) // it's still possible that we had a (T | U) or (T | nil) and not (nil | T)
auto rta = r->as<AstTypeReference>(); auto rta = r->as<AstTypeReference>();
@ -2555,15 +2772,39 @@ struct Printer
} }
} }
if (cstNode && cstNode->leadingPosition)
{
advance(*cstNode->leadingPosition);
writer.symbol("|");
}
size_t separatorIndex = 0;
for (size_t i = 0; i < a->types.size; ++i) for (size_t i = 0; i < a->types.size; ++i)
{ {
if (FFlag::LuauParseOptionalAsNode2)
{
if (const auto optional = a->types.data[i]->as<AstTypeOptional>())
{
advance(optional->location.begin);
writer.symbol("?");
continue;
}
}
if (i > 0) if (i > 0)
{ {
writer.maybeSpace(a->types.data[i]->location.begin, 2); if (cstNode && FFlag::LuauParseOptionalAsNode2)
{
// separatorIndex is only valid if `?` is handled as an AstTypeOptional
advance(cstNode->separatorPositions.data[separatorIndex]);
separatorIndex++;
}
else
writer.maybeSpace(a->types.data[i]->location.begin, 2);
writer.symbol("|"); writer.symbol("|");
} }
bool wrap = a->types.data[i]->as<AstTypeIntersection>() || a->types.data[i]->as<AstTypeFunction>(); bool wrap = !cstNode && (a->types.data[i]->as<AstTypeIntersection>() || a->types.data[i]->as<AstTypeFunction>());
if (wrap) if (wrap)
writer.symbol("("); writer.symbol("(");
@ -2576,15 +2817,27 @@ struct Printer
} }
else if (const auto& a = typeAnnotation.as<AstTypeIntersection>()) else if (const auto& a = typeAnnotation.as<AstTypeIntersection>())
{ {
const auto cstNode = lookupCstNode<CstTypeIntersection>(a);
// If the sizes are equal, we know there is a leading & token
if (cstNode && cstNode->leadingPosition)
{
advance(*cstNode->leadingPosition);
writer.symbol("&");
}
for (size_t i = 0; i < a->types.size; ++i) for (size_t i = 0; i < a->types.size; ++i)
{ {
if (i > 0) if (i > 0)
{ {
writer.maybeSpace(a->types.data[i]->location.begin, 2); if (cstNode)
advance(cstNode->separatorPositions.data[i - 1]);
else
writer.maybeSpace(a->types.data[i]->location.begin, 2);
writer.symbol("&"); writer.symbol("&");
} }
bool wrap = a->types.data[i]->as<AstTypeUnion>() || a->types.data[i]->as<AstTypeFunction>(); bool wrap = !cstNode && (a->types.data[i]->as<AstTypeUnion>() || a->types.data[i]->as<AstTypeFunction>());
if (wrap) if (wrap)
writer.symbol("("); writer.symbol("(");
@ -2599,7 +2852,7 @@ struct Printer
{ {
writer.symbol("("); writer.symbol("(");
visualizeTypeAnnotation(*a->type); visualizeTypeAnnotation(*a->type);
advance(Position{a->location.end.line, a->location.end.column - 1}); advanceBefore(a->location.end, 1);
writer.symbol(")"); writer.symbol(")");
} }
else if (const auto& a = typeAnnotation.as<AstTypeSingletonBool>()) else if (const auto& a = typeAnnotation.as<AstTypeSingletonBool>())
@ -2633,7 +2886,7 @@ std::string toString(AstNode* node)
StringWriter writer; StringWriter writer;
writer.pos = node->location.begin; writer.pos = node->location.begin;
if (FFlag::LuauStoreCSTData) if (FFlag::LuauStoreCSTData2)
{ {
Printer printer(writer, CstNodeMap{nullptr}); Printer printer(writer, CstNodeMap{nullptr});
printer.writeTypes = true; printer.writeTypes = true;
@ -2669,7 +2922,7 @@ void dump(AstNode* node)
std::string transpile(AstStatBlock& block, const CstNodeMap& cstNodeMap) std::string transpile(AstStatBlock& block, const CstNodeMap& cstNodeMap)
{ {
StringWriter writer; StringWriter writer;
if (FFlag::LuauStoreCSTData) if (FFlag::LuauStoreCSTData2)
{ {
Printer(writer, cstNodeMap).visualizeBlock(block); Printer(writer, cstNodeMap).visualizeBlock(block);
} }
@ -2683,7 +2936,7 @@ std::string transpile(AstStatBlock& block, const CstNodeMap& cstNodeMap)
std::string transpileWithTypes(AstStatBlock& block, const CstNodeMap& cstNodeMap) std::string transpileWithTypes(AstStatBlock& block, const CstNodeMap& cstNodeMap)
{ {
StringWriter writer; StringWriter writer;
if (FFlag::LuauStoreCSTData) if (FFlag::LuauStoreCSTData2)
{ {
Printer printer(writer, cstNodeMap); Printer printer(writer, cstNodeMap);
printer.writeTypes = true; printer.writeTypes = true;

View file

@ -407,41 +407,6 @@ PendingTypePack* TxnLog::changeLevel(TypePackId tp, TypeLevel newLevel)
return newTp; return newTp;
} }
PendingType* TxnLog::changeScope(TypeId ty, NotNull<Scope> newScope)
{
LUAU_ASSERT(get<FreeType>(ty) || get<TableType>(ty) || get<FunctionType>(ty));
PendingType* newTy = queue(ty);
if (FreeType* ftv = Luau::getMutable<FreeType>(newTy))
{
ftv->scope = newScope;
}
else if (TableType* ttv = Luau::getMutable<TableType>(newTy))
{
LUAU_ASSERT(ttv->state == TableState::Free || ttv->state == TableState::Generic);
ttv->scope = newScope;
}
else if (FunctionType* ftv = Luau::getMutable<FunctionType>(newTy))
{
ftv->scope = newScope;
}
return newTy;
}
PendingTypePack* TxnLog::changeScope(TypePackId tp, NotNull<Scope> newScope)
{
LUAU_ASSERT(get<FreeTypePack>(tp));
PendingTypePack* newTp = queue(tp);
if (FreeTypePack* ftp = Luau::getMutable<FreeTypePack>(newTp))
{
ftp->scope = newScope;
}
return newTp;
}
PendingType* TxnLog::changeIndexer(TypeId ty, std::optional<TableIndexer> indexer) PendingType* TxnLog::changeIndexer(TypeId ty, std::optional<TableIndexer> indexer)
{ {
LUAU_ASSERT(get<TableType>(ty)); LUAU_ASSERT(get<TableType>(ty));

View file

@ -488,11 +488,12 @@ FreeType::FreeType(TypeLevel level, TypeId lowerBound, TypeId upperBound)
{ {
} }
FreeType::FreeType(Scope* scope, TypeId lowerBound, TypeId upperBound) FreeType::FreeType(Scope* scope, TypeId lowerBound, TypeId upperBound, Polarity polarity)
: index(Unifiable::freshIndex()) : index(Unifiable::freshIndex())
, scope(scope) , scope(scope)
, lowerBound(lowerBound) , lowerBound(lowerBound)
, upperBound(upperBound) , upperBound(upperBound)
, polarity(polarity)
{ {
} }
@ -543,16 +544,18 @@ GenericType::GenericType(TypeLevel level)
{ {
} }
GenericType::GenericType(const Name& name) GenericType::GenericType(const Name& name, Polarity polarity)
: index(Unifiable::freshIndex()) : index(Unifiable::freshIndex())
, name(name) , name(name)
, explicitName(true) , explicitName(true)
, polarity(polarity)
{ {
} }
GenericType::GenericType(Scope* scope) GenericType::GenericType(Scope* scope, Polarity polarity)
: index(Unifiable::freshIndex()) : index(Unifiable::freshIndex())
, scope(scope) , scope(scope)
, polarity(polarity)
{ {
} }
@ -630,23 +633,6 @@ FunctionType::FunctionType(TypeLevel level, TypePackId argTypes, TypePackId retT
{ {
} }
FunctionType::FunctionType(
TypeLevel level,
Scope* scope,
TypePackId argTypes,
TypePackId retTypes,
std::optional<FunctionDefinition> defn,
bool hasSelf
)
: definition(std::move(defn))
, level(level)
, scope(scope)
, argTypes(argTypes)
, retTypes(retTypes)
, hasSelf(hasSelf)
{
}
FunctionType::FunctionType( FunctionType::FunctionType(
std::vector<TypeId> generics, std::vector<TypeId> generics,
std::vector<TypePackId> genericPacks, std::vector<TypePackId> genericPacks,
@ -683,27 +669,6 @@ FunctionType::FunctionType(
{ {
} }
FunctionType::FunctionType(
TypeLevel level,
Scope* scope,
std::vector<TypeId> generics,
std::vector<TypePackId> genericPacks,
TypePackId argTypes,
TypePackId retTypes,
std::optional<FunctionDefinition> defn,
bool hasSelf
)
: definition(std::move(defn))
, generics(generics)
, genericPacks(genericPacks)
, level(level)
, scope(scope)
, argTypes(argTypes)
, retTypes(retTypes)
, hasSelf(hasSelf)
{
}
Property::Property() {} Property::Property() {}
Property::Property( Property::Property(
@ -1306,9 +1271,9 @@ IntersectionTypeIterator end(const IntersectionType* itv)
return IntersectionTypeIterator{}; return IntersectionTypeIterator{};
} }
TypeId freshType(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, Scope* scope) TypeId freshType(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, Scope* scope, Polarity polarity)
{ {
return arena->addType(FreeType{scope, builtinTypes->neverType, builtinTypes->unknownType}); return arena->addType(FreeType{scope, builtinTypes->neverType, builtinTypes->unknownType, polarity});
} }
std::vector<TypeId> filterMap(TypeId type, TypeIdPredicate predicate) std::vector<TypeId> filterMap(TypeId type, TypeIdPredicate predicate)

View file

@ -77,9 +77,9 @@ TypeId TypeArena::freshType_DEPRECATED(Scope* scope, TypeLevel level)
return allocated; return allocated;
} }
TypePackId TypeArena::freshTypePack(Scope* scope) TypePackId TypeArena::freshTypePack(Scope* scope, Polarity polarity)
{ {
TypePackId allocated = typePacks.allocate(FreeTypePack{scope}); TypePackId allocated = typePacks.allocate(FreeTypePack{scope, polarity});
asMutable(allocated)->owningArena = this; asMutable(allocated)->owningArena = this;

View file

@ -13,6 +13,8 @@
#include <string> #include <string>
LUAU_FASTFLAG(LuauStoreCSTData2)
static char* allocateString(Luau::Allocator& allocator, std::string_view contents) static char* allocateString(Luau::Allocator& allocator, std::string_view contents)
{ {
char* result = (char*)allocator.allocate(contents.size() + 1); char* result = (char*)allocator.allocate(contents.size() + 1);
@ -305,7 +307,8 @@ public:
std::optional<AstArgumentName>* arg = &argNames.data[i++]; std::optional<AstArgumentName>* arg = &argNames.data[i++];
if (el) if (el)
new (arg) std::optional<AstArgumentName>(AstArgumentName(AstName(el->name.c_str()), el->location)); new (arg)
std::optional<AstArgumentName>(AstArgumentName(AstName(el->name.c_str()), FFlag::LuauStoreCSTData2 ? Location() : el->location));
else else
new (arg) std::optional<AstArgumentName>(); new (arg) std::optional<AstArgumentName>();
} }

View file

@ -26,10 +26,14 @@
#include "Luau/VisitType.h" #include "Luau/VisitType.h"
#include <algorithm> #include <algorithm>
#include <sstream>
LUAU_FASTFLAG(DebugLuauMagicTypes) LUAU_FASTFLAG(DebugLuauMagicTypes)
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds) LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
LUAU_FASTFLAGVARIABLE(LuauImproveTypePathsInErrors)
LUAU_FASTFLAG(LuauUserTypeFunTypecheck)
LUAU_FASTFLAGVARIABLE(LuauTypeCheckerAcceptNumberConcats)
namespace Luau namespace Luau
{ {
@ -1201,7 +1205,8 @@ void TypeChecker2::visit(AstStatTypeAlias* stat)
void TypeChecker2::visit(AstStatTypeFunction* stat) void TypeChecker2::visit(AstStatTypeFunction* stat)
{ {
// TODO: add type checking for user-defined type functions if (FFlag::LuauUserTypeFunTypecheck)
visit(stat->body);
} }
void TypeChecker2::visit(AstTypeList types) void TypeChecker2::visit(AstTypeList types)
@ -2225,10 +2230,21 @@ TypeId TypeChecker2::visit(AstExprBinary* expr, AstNode* overrideKey)
return builtinTypes->numberType; return builtinTypes->numberType;
case AstExprBinary::Op::Concat: case AstExprBinary::Op::Concat:
testIsSubtype(leftType, builtinTypes->stringType, expr->left->location); {
testIsSubtype(rightType, builtinTypes->stringType, expr->right->location); if (FFlag::LuauTypeCheckerAcceptNumberConcats)
{
const TypeId numberOrString = module->internalTypes.addType(UnionType{{builtinTypes->numberType, builtinTypes->stringType}});
testIsSubtype(leftType, numberOrString, expr->left->location);
testIsSubtype(rightType, numberOrString, expr->right->location);
}
else
{
testIsSubtype(leftType, builtinTypes->stringType, expr->left->location);
testIsSubtype(rightType, builtinTypes->stringType, expr->right->location);
}
return builtinTypes->stringType; return builtinTypes->stringType;
}
case AstExprBinary::Op::CompareGe: case AstExprBinary::Op::CompareGe:
case AstExprBinary::Op::CompareGt: case AstExprBinary::Op::CompareGt:
case AstExprBinary::Op::CompareLe: case AstExprBinary::Op::CompareLe:
@ -2701,20 +2717,61 @@ Reasonings TypeChecker2::explainReasonings_(TID subTy, TID superTy, Location loc
if (!subLeafTy && !superLeafTy && !subLeafTp && !superLeafTp) if (!subLeafTy && !superLeafTy && !subLeafTp && !superLeafTp)
ice->ice("Subtyping test returned a reasoning where one path ends at a type and the other ends at a pack.", location); ice->ice("Subtyping test returned a reasoning where one path ends at a type and the other ends at a pack.", location);
std::string relation = "a subtype of"; if (FFlag::LuauImproveTypePathsInErrors)
if (reasoning.variance == SubtypingVariance::Invariant) {
relation = "exactly"; std::string relation = "a subtype of";
else if (reasoning.variance == SubtypingVariance::Contravariant) if (reasoning.variance == SubtypingVariance::Invariant)
relation = "a supertype of"; relation = "exactly";
else if (reasoning.variance == SubtypingVariance::Contravariant)
relation = "a supertype of";
std::string reason; std::string subLeafAsString = toString(subLeaf);
if (reasoning.subPath == reasoning.superPath) // if the string is empty, it must be an empty type pack
reason = "at " + toString(reasoning.subPath) + ", " + toString(subLeaf) + " is not " + relation + " " + toString(superLeaf); if (subLeafAsString.empty())
subLeafAsString = "()";
std::string superLeafAsString = toString(superLeaf);
// if the string is empty, it must be an empty type pack
if (superLeafAsString.empty())
superLeafAsString = "()";
std::stringstream baseReasonBuilder;
baseReasonBuilder << "`" << subLeafAsString << "` is not " << relation << " `" << superLeafAsString << "`";
std::string baseReason = baseReasonBuilder.str();
std::stringstream reason;
if (reasoning.subPath == reasoning.superPath)
reason << toStringHuman(reasoning.subPath) << "`" << subLeafAsString << "` in the former type and `" << superLeafAsString
<< "` in the latter type, and " << baseReason;
else if (!reasoning.subPath.empty() && !reasoning.superPath.empty())
reason << toStringHuman(reasoning.subPath) << "`" << subLeafAsString << "` and " << toStringHuman(reasoning.superPath) << "`"
<< superLeafAsString << "`, and " << baseReason;
else if (!reasoning.subPath.empty())
reason << toStringHuman(reasoning.subPath) << "`" << subLeafAsString << "`, which is not " << relation << " `" << superLeafAsString
<< "`";
else
reason << toStringHuman(reasoning.superPath) << "`" << superLeafAsString << "`, and " << baseReason;
reasons.push_back(reason.str());
}
else else
reason = "type " + toString(subTy) + toString(reasoning.subPath, /* prefixDot */ true) + " (" + toString(subLeaf) + ") is not " + {
relation + " " + toString(superTy) + toString(reasoning.superPath, /* prefixDot */ true) + " (" + toString(superLeaf) + ")"; std::string relation = "a subtype of";
if (reasoning.variance == SubtypingVariance::Invariant)
relation = "exactly";
else if (reasoning.variance == SubtypingVariance::Contravariant)
relation = "a supertype of";
reasons.push_back(reason); std::string reason;
if (reasoning.subPath == reasoning.superPath)
reason = "at " + toString(reasoning.subPath) + ", " + toString(subLeaf) + " is not " + relation + " " + toString(superLeaf);
else
reason = "type " + toString(subTy) + toString(reasoning.subPath, /* prefixDot */ true) + " (" + toString(subLeaf) + ") is not " +
relation + " " + toString(superTy) + toString(reasoning.superPath, /* prefixDot */ true) + " (" + toString(superLeaf) + ")";
reasons.push_back(reason);
}
// if we haven't already proved this isn't suppressing, we have to keep checking. // if we haven't already proved this isn't suppressing, we have to keep checking.
if (suppressed) if (suppressed)

File diff suppressed because it is too large Load diff

View file

@ -13,11 +13,7 @@
#include <set> #include <set>
#include <vector> #include <vector>
LUAU_FASTFLAGVARIABLE(LuauTypeFunFixHydratedClasses)
LUAU_DYNAMIC_FASTINT(LuauTypeFunctionSerdeIterationLimit) LUAU_DYNAMIC_FASTINT(LuauTypeFunctionSerdeIterationLimit)
LUAU_FASTFLAGVARIABLE(LuauTypeFunSingletonEquality)
LUAU_FASTFLAGVARIABLE(LuauUserTypeFunTypeofReturnsType)
LUAU_FASTFLAGVARIABLE(LuauTypeFunPrintFix)
LUAU_FASTFLAGVARIABLE(LuauTypeFunReadWriteParents) LUAU_FASTFLAGVARIABLE(LuauTypeFunReadWriteParents)
namespace Luau namespace Luau
@ -1617,11 +1613,8 @@ void registerTypeUserData(lua_State* L)
// Create and register metatable for type userdata // Create and register metatable for type userdata
luaL_newmetatable(L, "type"); luaL_newmetatable(L, "type");
if (FFlag::LuauUserTypeFunTypeofReturnsType) lua_pushstring(L, "type");
{ lua_setfield(L, -2, "__type");
lua_pushstring(L, "type");
lua_setfield(L, -2, "__type");
}
// Protect metatable from being changed // Protect metatable from being changed
lua_pushstring(L, "The metatable is locked"); lua_pushstring(L, "The metatable is locked");
@ -1662,10 +1655,7 @@ static int print(lua_State* L)
const char* s = luaL_tolstring(L, i, &l); // convert to string using __tostring et al const char* s = luaL_tolstring(L, i, &l); // convert to string using __tostring et al
if (i > 1) if (i > 1)
{ {
if (FFlag::LuauTypeFunPrintFix) result.append(1, '\t');
result.append(1, '\t');
else
result.append('\t', 1);
} }
result.append(s, l); result.append(s, l);
lua_pop(L, 1); lua_pop(L, 1);
@ -1758,14 +1748,14 @@ bool areEqual(SeenSet& seen, const TypeFunctionSingletonType& lhs, const TypeFun
{ {
const TypeFunctionBooleanSingleton* lp = get<TypeFunctionBooleanSingleton>(&lhs); const TypeFunctionBooleanSingleton* lp = get<TypeFunctionBooleanSingleton>(&lhs);
const TypeFunctionBooleanSingleton* rp = get<TypeFunctionBooleanSingleton>(FFlag::LuauTypeFunSingletonEquality ? &rhs : &lhs); const TypeFunctionBooleanSingleton* rp = get<TypeFunctionBooleanSingleton>(&rhs);
if (lp && rp) if (lp && rp)
return lp->value == rp->value; return lp->value == rp->value;
} }
{ {
const TypeFunctionStringSingleton* lp = get<TypeFunctionStringSingleton>(&lhs); const TypeFunctionStringSingleton* lp = get<TypeFunctionStringSingleton>(&lhs);
const TypeFunctionStringSingleton* rp = get<TypeFunctionStringSingleton>(FFlag::LuauTypeFunSingletonEquality ? &rhs : &lhs); const TypeFunctionStringSingleton* rp = get<TypeFunctionStringSingleton>(&rhs);
if (lp && rp) if (lp && rp)
return lp->value == rp->value; return lp->value == rp->value;
} }
@ -1918,10 +1908,7 @@ bool areEqual(SeenSet& seen, const TypeFunctionClassType& lhs, const TypeFunctio
if (seenSetContains(seen, &lhs, &rhs)) if (seenSetContains(seen, &lhs, &rhs))
return true; return true;
if (FFlag::LuauTypeFunFixHydratedClasses) return lhs.classTy == rhs.classTy;
return lhs.classTy == rhs.classTy;
else
return lhs.name_DEPRECATED == rhs.name_DEPRECATED;
} }
bool areEqual(SeenSet& seen, const TypeFunctionType& lhs, const TypeFunctionType& rhs) bool areEqual(SeenSet& seen, const TypeFunctionType& lhs, const TypeFunctionType& rhs)

View file

@ -19,7 +19,6 @@
// used to control the recursion limit of any operations done by user-defined type functions // used to control the recursion limit of any operations done by user-defined type functions
// currently, controls serialization, deserialization, and `type.copy` // currently, controls serialization, deserialization, and `type.copy`
LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFunctionSerdeIterationLimit, 100'000); LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFunctionSerdeIterationLimit, 100'000);
LUAU_FASTFLAG(LuauTypeFunFixHydratedClasses)
LUAU_FASTFLAG(LuauTypeFunReadWriteParents) LUAU_FASTFLAG(LuauTypeFunReadWriteParents)
namespace Luau namespace Luau
@ -209,19 +208,11 @@ private:
} }
else if (auto c = get<ClassType>(ty)) else if (auto c = get<ClassType>(ty))
{ {
if (FFlag::LuauTypeFunFixHydratedClasses) // 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
// Since there aren't any new class types being created in type functions, we will deserialize by using a direct reference to the target = typeFunctionRuntime->typeArena.allocate(
// original class TypeFunctionClassType{{}, std::nullopt, std::nullopt, std::nullopt, std::nullopt, std::nullopt, ty}
target = typeFunctionRuntime->typeArena.allocate(TypeFunctionClassType{{}, std::nullopt, std::nullopt, std::nullopt, std::nullopt, std::nullopt, ty}); );
}
else
{
state->classesSerialized_DEPRECATED[c->name] = ty;
target = typeFunctionRuntime->typeArena.allocate(
TypeFunctionClassType{{}, std::nullopt, std::nullopt, std::nullopt, std::nullopt, std::nullopt, /* classTy */ nullptr, c->name}
);
}
} }
else if (auto g = get<GenericType>(ty)) else if (auto g = get<GenericType>(ty))
{ {
@ -713,17 +704,7 @@ private:
} }
else if (auto c = get<TypeFunctionClassType>(ty)) else if (auto c = get<TypeFunctionClassType>(ty))
{ {
if (FFlag::LuauTypeFunFixHydratedClasses) target = c->classTy;
{
target = c->classTy;
}
else
{
if (auto result = state->classesSerialized_DEPRECATED.find(c->name_DEPRECATED))
target = *result;
else
state->ctx->ice->ice("Deserializing user defined type function arguments: mysterious class type is being deserialized");
}
} }
else if (auto g = get<TypeFunctionGenericType>(ty)) else if (auto g = get<TypeFunctionGenericType>(ty))
{ {

View file

@ -32,9 +32,10 @@ LUAU_FASTINTVARIABLE(LuauVisitRecursionLimit, 500)
LUAU_FASTFLAG(LuauKnowsTheDataModel3) LUAU_FASTFLAG(LuauKnowsTheDataModel3)
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification) LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification)
LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAGVARIABLE(LuauOldSolverCreatesChildScopePointers)
LUAU_FASTFLAG(LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType) LUAU_FASTFLAG(LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds) LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
LUAU_FASTFLAG(LuauRetainDefinitionAliasLocations)
LUAU_FASTFLAGVARIABLE(LuauStatForInFix)
namespace Luau namespace Luau
{ {
@ -255,6 +256,7 @@ ModulePtr TypeChecker::checkWithoutRecursionCheck(const SourceModule& module, Mo
currentModule->type = module.type; currentModule->type = module.type;
currentModule->allocator = module.allocator; currentModule->allocator = module.allocator;
currentModule->names = module.names; currentModule->names = module.names;
currentModule->root = module.root;
iceHandler->moduleName = module.name; iceHandler->moduleName = module.name;
normalizer.arena = &currentModule->internalTypes; normalizer.arena = &currentModule->internalTypes;
@ -1316,8 +1318,24 @@ ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatForIn& forin)
// Extract the remaining return values of the call // Extract the remaining return values of the call
// and check them against the parameter types of the iterator function. // and check them against the parameter types of the iterator function.
auto [types, tail] = flatten(callRetPack); auto [types, tail] = flatten(callRetPack);
std::vector<TypeId> argTypes = std::vector<TypeId>(types.begin() + 1, types.end());
argPack = addTypePack(TypePackVar{TypePack{std::move(argTypes), tail}}); if (FFlag::LuauStatForInFix)
{
if (!types.empty())
{
std::vector<TypeId> argTypes = std::vector<TypeId>(types.begin() + 1, types.end());
argPack = addTypePack(TypePackVar{TypePack{std::move(argTypes), tail}});
}
else
{
argPack = addTypePack(TypePack{});
}
}
else
{
std::vector<TypeId> argTypes = std::vector<TypeId>(types.begin() + 1, types.end());
argPack = addTypePack(TypePackVar{TypePack{std::move(argTypes), tail}});
}
} }
else else
{ {
@ -1655,7 +1673,10 @@ void TypeChecker::prototype(const ScopePtr& scope, const AstStatTypeAlias& typea
FreeType* ftv = getMutable<FreeType>(ty); FreeType* ftv = getMutable<FreeType>(ty);
LUAU_ASSERT(ftv); LUAU_ASSERT(ftv);
ftv->forwardedTypeAlias = true; ftv->forwardedTypeAlias = true;
bindingsMap[name] = {std::move(generics), std::move(genericPacks), ty}; if (FFlag::LuauRetainDefinitionAliasLocations)
bindingsMap[name] = {std::move(generics), std::move(genericPacks), ty, typealias.location};
else
bindingsMap[name] = {std::move(generics), std::move(genericPacks), ty};
scope->typeAliasLocations[name] = typealias.location; scope->typeAliasLocations[name] = typealias.location;
scope->typeAliasNameLocations[name] = typealias.nameLocation; scope->typeAliasNameLocations[name] = typealias.nameLocation;
@ -1700,7 +1721,10 @@ void TypeChecker::prototype(const ScopePtr& scope, const AstStatDeclareClass& de
TypeId metaTy = addType(TableType{TableState::Sealed, scope->level}); TypeId metaTy = addType(TableType{TableState::Sealed, scope->level});
ctv->metatable = metaTy; ctv->metatable = metaTy;
scope->exportedTypeBindings[className] = TypeFun{{}, classTy}; if (FFlag::LuauRetainDefinitionAliasLocations)
scope->exportedTypeBindings[className] = TypeFun{{}, classTy, declaredClass.location};
else
scope->exportedTypeBindings[className] = TypeFun{{}, classTy};
} }
ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatDeclareClass& declaredClass) ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatDeclareClass& declaredClass)
@ -5212,12 +5236,9 @@ LUAU_NOINLINE void TypeChecker::reportErrorCodeTooComplex(const Location& locati
ScopePtr TypeChecker::childFunctionScope(const ScopePtr& parent, const Location& location, int subLevel) ScopePtr TypeChecker::childFunctionScope(const ScopePtr& parent, const Location& location, int subLevel)
{ {
ScopePtr scope = std::make_shared<Scope>(parent, subLevel); ScopePtr scope = std::make_shared<Scope>(parent, subLevel);
if (FFlag::LuauOldSolverCreatesChildScopePointers) scope->location = location;
{ scope->returnType = parent->returnType;
scope->location = location; parent->children.emplace_back(scope.get());
scope->returnType = parent->returnType;
parent->children.emplace_back(scope.get());
}
currentModule->scopes.push_back(std::make_pair(location, scope)); currentModule->scopes.push_back(std::make_pair(location, scope));
return scope; return scope;
@ -5229,12 +5250,9 @@ ScopePtr TypeChecker::childScope(const ScopePtr& parent, const Location& locatio
ScopePtr scope = std::make_shared<Scope>(parent); ScopePtr scope = std::make_shared<Scope>(parent);
scope->level = parent->level; scope->level = parent->level;
scope->varargPack = parent->varargPack; scope->varargPack = parent->varargPack;
if (FFlag::LuauOldSolverCreatesChildScopePointers) scope->location = location;
{ scope->returnType = parent->returnType;
scope->location = location; parent->children.emplace_back(scope.get());
scope->returnType = parent->returnType;
parent->children.emplace_back(scope.get());
}
currentModule->scopes.push_back(std::make_pair(location, scope)); currentModule->scopes.push_back(std::make_pair(location, scope));
return scope; return scope;
@ -5724,6 +5742,10 @@ TypeId TypeChecker::resolveTypeWorker(const ScopePtr& scope, const AstType& anno
TypeId ty = checkExpr(scope, *typeOf->expr).type; TypeId ty = checkExpr(scope, *typeOf->expr).type;
return ty; return ty;
} }
else if (annotation.is<AstTypeOptional>())
{
return builtinTypes->nilType;
}
else if (const auto& un = annotation.as<AstTypeUnion>()) else if (const auto& un = annotation.as<AstTypeUnion>())
{ {
if (FFlag::LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType) if (FFlag::LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)

View file

@ -6,7 +6,7 @@
#include <stdexcept> #include <stdexcept>
LUAU_FASTFLAG(LuauSolverV2); LUAU_FASTFLAGVARIABLE(LuauTypePackDetectCycles)
namespace Luau namespace Luau
{ {
@ -18,10 +18,11 @@ FreeTypePack::FreeTypePack(TypeLevel level)
{ {
} }
FreeTypePack::FreeTypePack(Scope* scope) FreeTypePack::FreeTypePack(Scope* scope, Polarity polarity)
: index(Unifiable::freshIndex()) : index(Unifiable::freshIndex())
, level{} , level{}
, scope(scope) , scope(scope)
, polarity(polarity)
{ {
} }
@ -52,9 +53,10 @@ GenericTypePack::GenericTypePack(const Name& name)
{ {
} }
GenericTypePack::GenericTypePack(Scope* scope) GenericTypePack::GenericTypePack(Scope* scope, Polarity polarity)
: index(Unifiable::freshIndex()) : index(Unifiable::freshIndex())
, scope(scope) , scope(scope)
, polarity(polarity)
{ {
} }
@ -147,6 +149,15 @@ TypePackIterator& TypePackIterator::operator++()
currentTypePack = tp->tail ? log->follow(*tp->tail) : nullptr; currentTypePack = tp->tail ? log->follow(*tp->tail) : nullptr;
tp = currentTypePack ? log->getMutable<TypePack>(currentTypePack) : nullptr; tp = currentTypePack ? log->getMutable<TypePack>(currentTypePack) : nullptr;
if (FFlag::LuauTypePackDetectCycles && tp)
{
// Step twice on each iteration to detect cycles
tailCycleCheck = tp->tail ? log->follow(*tp->tail) : nullptr;
if (currentTypePack == tailCycleCheck)
throw InternalCompilerError("TypePackIterator detected a type pack cycle");
}
currentIndex = 0; currentIndex = 0;
} }

View file

@ -14,7 +14,8 @@
#include <type_traits> #include <type_traits>
LUAU_FASTFLAG(LuauSolverV2); LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTFLAGVARIABLE(LuauDisableNewSolverAssertsInMixedMode); LUAU_FASTFLAGVARIABLE(LuauDisableNewSolverAssertsInMixedMode)
// Maximum number of steps to follow when traversing a path. May not always // Maximum number of steps to follow when traversing a path. May not always
// equate to the number of components in a path, depending on the traversal // equate to the number of components in a path, depending on the traversal
// logic. // logic.
@ -638,6 +639,247 @@ std::string toString(const TypePath::Path& path, bool prefixDot)
return result.str(); return result.str();
} }
std::string toStringHuman(const TypePath::Path& path)
{
LUAU_ASSERT(FFlag::LuauSolverV2);
enum class State
{
Initial,
Normal,
Property,
PendingIs,
PendingAs,
PendingWhich,
};
std::stringstream result;
State state = State::Initial;
bool last = false;
auto strComponent = [&](auto&& c)
{
using T = std::decay_t<decltype(c)>;
if constexpr (std::is_same_v<T, TypePath::Property>)
{
if (state == State::PendingIs)
result << ", ";
switch (state)
{
case State::Initial:
case State::PendingIs:
if (c.isRead)
result << "accessing `";
else
result << "writing to `";
break;
case State::Property:
// if the previous state was a property, then we're doing a sequence of indexing
result << '.';
break;
default:
break;
}
result << c.name;
state = State::Property;
}
else if constexpr (std::is_same_v<T, TypePath::Index>)
{
size_t humanIndex = c.index + 1;
if (state == State::Initial && !last)
result << "in" << ' ';
else if (state == State::PendingIs)
result << ' ' << "has" << ' ';
else if (state == State::Property)
result << '`' << ' ' << "has" << ' ';
result << "the " << humanIndex;
switch (humanIndex)
{
case 1:
result << "st";
break;
case 2:
result << "nd";
break;
case 3:
result << "rd";
break;
default:
result << "th";
}
switch (c.variant)
{
case TypePath::Index::Variant::Pack:
result << ' ' << "entry in the type pack";
break;
case TypePath::Index::Variant::Union:
result << ' ' << "component of the union";
break;
case TypePath::Index::Variant::Intersection:
result << ' ' << "component of the intersection";
break;
}
if (state == State::PendingWhich)
result << ' ' << "which";
if (state == State::PendingIs || state == State::Property)
state = State::PendingAs;
else
state = State::PendingIs;
}
else if constexpr (std::is_same_v<T, TypePath::TypeField>)
{
if (state == State::Initial && !last)
result << "in" << ' ';
else if (state == State::PendingIs)
result << ", ";
else if (state == State::Property)
result << '`' << ' ' << "has" << ' ';
switch (c)
{
case TypePath::TypeField::Table:
result << "the table portion";
if (state == State::Property)
state = State::PendingAs;
else
state = State::PendingIs;
break;
case TypePath::TypeField::Metatable:
result << "the metatable portion";
if (state == State::Property)
state = State::PendingAs;
else
state = State::PendingIs;
break;
case TypePath::TypeField::LowerBound:
result << "the lower bound of" << ' ';
state = State::Normal;
break;
case TypePath::TypeField::UpperBound:
result << "the upper bound of" << ' ';
state = State::Normal;
break;
case TypePath::TypeField::IndexLookup:
result << "the index type";
if (state == State::Property)
state = State::PendingAs;
else
state = State::PendingIs;
break;
case TypePath::TypeField::IndexResult:
result << "the result of indexing";
if (state == State::Property)
state = State::PendingAs;
else
state = State::PendingIs;
break;
case TypePath::TypeField::Negated:
result << "the negation" << ' ';
state = State::Normal;
break;
case TypePath::TypeField::Variadic:
result << "the variadic" << ' ';
state = State::Normal;
break;
}
}
else if constexpr (std::is_same_v<T, TypePath::PackField>)
{
if (state == State::PendingIs)
result << ", ";
else if (state == State::Property)
result << "`, ";
switch (c)
{
case TypePath::PackField::Arguments:
if (state == State::Initial)
result << "it" << ' ';
else if (state == State::PendingIs)
result << "the function" << ' ';
result << "takes";
break;
case TypePath::PackField::Returns:
if (state == State::Initial)
result << "it" << ' ';
else if (state == State::PendingIs)
result << "the function" << ' ';
result << "returns";
break;
case TypePath::PackField::Tail:
if (state == State::Initial)
result << "it has" << ' ';
result << "a tail of";
break;
}
if (state == State::PendingIs)
{
result << ' ';
state = State::PendingWhich;
}
else
{
result << ' ';
state = State::Normal;
}
}
else if constexpr (std::is_same_v<T, TypePath::Reduction>)
{
if (state == State::Initial)
result << "it" << ' ';
result << "reduces to" << ' ';
state = State::Normal;
}
else
{
static_assert(always_false_v<T>, "Unhandled Component variant");
}
};
size_t count = 0;
for (const TypePath::Component& component : path.components)
{
count++;
if (count == path.components.size())
last = true;
Luau::visit(strComponent, component);
}
switch (state)
{
case State::Property:
result << "` results in ";
break;
case State::PendingWhich:
// pending `which` becomes `is` if it's at the end
result << "is" << ' ';
break;
case State::PendingIs:
result << ' ' << "is" << ' ';
break;
case State::PendingAs:
result << ' ' << "as" << ' ';
break;
default:
break;
}
return result.str();
}
static bool traverse(TraversalState& state, const Path& path) static bool traverse(TraversalState& state, const Path& path)
{ {
auto step = [&state](auto&& c) auto step = [&state](auto&& c)

View file

@ -14,7 +14,9 @@ LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTFLAG(LuauAutocompleteRefactorsForIncrementalAutocomplete); LUAU_FASTFLAG(LuauAutocompleteRefactorsForIncrementalAutocomplete);
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope); LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope);
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds) LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
LUAU_FASTFLAG(LuauNonReentrantGeneralization)
LUAU_FASTFLAG(LuauDisableNewSolverAssertsInMixedMode) LUAU_FASTFLAG(LuauDisableNewSolverAssertsInMixedMode)
namespace Luau namespace Luau
{ {
@ -304,7 +306,11 @@ TypePack extendTypePack(
// also have to create a new tail. // also have to create a new tail.
TypePack newPack; TypePack newPack;
newPack.tail = arena.freshTypePack(ftp->scope); newPack.tail = arena.freshTypePack(ftp->scope, ftp->polarity);
if (FFlag::LuauNonReentrantGeneralization)
trackInteriorFreeTypePack(ftp->scope, *newPack.tail);
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
result.tail = newPack.tail; result.tail = newPack.tail;
size_t overridesIndex = 0; size_t overridesIndex = 0;
@ -319,7 +325,7 @@ TypePack extendTypePack(
{ {
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
{ {
FreeType ft{ftp->scope, builtinTypes->neverType, builtinTypes->unknownType}; FreeType ft{ftp->scope, builtinTypes->neverType, builtinTypes->unknownType, ftp->polarity};
t = arena.addType(ft); t = arena.addType(ft);
if (FFlag::LuauTrackInteriorFreeTypesOnScope) if (FFlag::LuauTrackInteriorFreeTypesOnScope)
trackInteriorFreeType(ftp->scope, t); trackInteriorFreeType(ftp->scope, t);
@ -568,4 +574,24 @@ void trackInteriorFreeType(Scope* scope, TypeId ty)
LUAU_ASSERT(!"No scopes in parent chain had a present `interiorFreeTypes` member."); LUAU_ASSERT(!"No scopes in parent chain had a present `interiorFreeTypes` member.");
} }
void trackInteriorFreeTypePack(Scope* scope, TypePackId tp)
{
LUAU_ASSERT(tp);
if (!FFlag::LuauNonReentrantGeneralization)
return;
for (; scope; scope = scope->parent.get())
{
if (scope->interiorFreeTypePacks)
{
scope->interiorFreeTypePacks->push_back(tp);
return;
}
}
// There should at least be *one* generalization constraint per module
// where `interiorFreeTypes` is present, which would be the one made
// by ConstraintGenerator::visitModuleRoot.
LUAU_ASSERT(!"No scopes in parent chain had a present `interiorFreeTypePacks` member.");
}
} // namespace Luau } // namespace Luau

View file

@ -24,6 +24,7 @@ const size_t kPageSize = sysconf(_SC_PAGESIZE);
#endif #endif
#endif #endif
#include <stdint.h>
#include <stdlib.h> #include <stdlib.h>
LUAU_FASTFLAG(DebugLuauFreezeArena) LUAU_FASTFLAG(DebugLuauFreezeArena)

View file

@ -18,6 +18,9 @@
#include <optional> #include <optional>
LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAGVARIABLE(LuauUnifyMetatableWithAny)
LUAU_FASTFLAG(LuauExtraFollows)
LUAU_FASTFLAG(LuauNonReentrantGeneralization)
namespace Luau namespace Luau
{ {
@ -235,6 +238,10 @@ 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)
return unify(subMetatable, superAny);
else if (FFlag::LuauUnifyMetatableWithAny && 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);
else if (superMetatable) // if we only have one metatable, unify with the inner table else if (superMetatable) // if we only have one metatable, unify with the inner table
@ -277,7 +284,7 @@ bool Unifier2::unifyFreeWithType(TypeId subTy, TypeId superTy)
if (superArgTail) if (superArgTail)
return doDefault(); return doDefault();
const IntersectionType* upperBoundIntersection = get<IntersectionType>(subFree->upperBound); const IntersectionType* upperBoundIntersection = get<IntersectionType>(FFlag::LuauExtraFollows ? upperBound : subFree->upperBound);
if (!upperBoundIntersection) if (!upperBoundIntersection)
return doDefault(); return doDefault();
@ -315,10 +322,23 @@ bool Unifier2::unify(TypeId subTy, const FunctionType* superFn)
if (shouldInstantiate) if (shouldInstantiate)
{ {
for (auto generic : subFn->generics) for (auto generic : subFn->generics)
genericSubstitutions[generic] = freshType(arena, builtinTypes, scope); {
const GenericType* gen = get<GenericType>(generic);
LUAU_ASSERT(gen);
genericSubstitutions[generic] = freshType(scope, gen->polarity);
}
for (auto genericPack : subFn->genericPacks) for (auto genericPack : subFn->genericPacks)
genericPackSubstitutions[genericPack] = arena->freshTypePack(scope); {
if (FFlag::LuauNonReentrantGeneralization)
{
const GenericTypePack* gen = get<GenericTypePack>(genericPack);
LUAU_ASSERT(gen);
genericPackSubstitutions[genericPack] = freshTypePack(scope, gen->polarity);
}
else
genericPackSubstitutions[genericPack] = arena->freshTypePack(scope);
}
} }
bool argResult = unify(superFn->argTypes, subFn->argTypes); bool argResult = unify(superFn->argTypes, subFn->argTypes);
@ -427,9 +447,6 @@ bool Unifier2::unify(TableType* subTable, const TableType* superTable)
superTypePackParamsIter++; superTypePackParamsIter++;
} }
if (subTable->selfTy && superTable->selfTy)
result &= unify(*subTable->selfTy, *superTable->selfTy);
if (subTable->indexer && superTable->indexer) if (subTable->indexer && superTable->indexer)
{ {
result &= unify(subTable->indexer->indexType, superTable->indexer->indexType); result &= unify(subTable->indexer->indexType, superTable->indexer->indexType);
@ -524,6 +541,16 @@ bool Unifier2::unify(const TableType* subTable, const AnyType* superAny)
return true; return true;
} }
bool Unifier2::unify(const MetatableType* subMetatable, const AnyType*)
{
return unify(subMetatable->metatable, builtinTypes->anyType) && unify(subMetatable->table, builtinTypes->anyType);
}
bool Unifier2::unify(const AnyType*, const MetatableType* superMetatable)
{
return unify(builtinTypes->anyType, superMetatable->metatable) && unify(builtinTypes->anyType, superMetatable->table);
}
// FIXME? This should probably return an ErrorVec or an optional<TypeError> // FIXME? This should probably return an ErrorVec or an optional<TypeError>
// rather than a boolean to signal an occurs check failure. // rather than a boolean to signal an occurs check failure.
bool Unifier2::unify(TypePackId subTp, TypePackId superTp) bool Unifier2::unify(TypePackId subTp, TypePackId superTp)
@ -634,38 +661,33 @@ struct FreeTypeSearcher : TypeVisitor
{ {
} }
enum Polarity Polarity polarity = Polarity::Positive;
{
Positive,
Negative,
Both,
};
Polarity polarity = Positive;
void flip() void flip()
{ {
switch (polarity) switch (polarity)
{ {
case Positive: case Polarity::Positive:
polarity = Negative; polarity = Polarity::Negative;
break; break;
case Negative: case Polarity::Negative:
polarity = Positive; polarity = Polarity::Positive;
break; break;
case Both: case Polarity::Mixed:
break; break;
default:
LUAU_ASSERT(!"Unreachable");
} }
} }
DenseHashSet<const void*> seenPositive{nullptr}; DenseHashSet<const void*> seenPositive{nullptr};
DenseHashSet<const void*> seenNegative{nullptr}; DenseHashSet<const void*> seenNegative{nullptr};
bool seenWithPolarity(const void* ty) bool seenWithCurrentPolarity(const void* ty)
{ {
switch (polarity) switch (polarity)
{ {
case Positive: case Polarity::Positive:
{ {
if (seenPositive.contains(ty)) if (seenPositive.contains(ty))
return true; return true;
@ -673,7 +695,7 @@ struct FreeTypeSearcher : TypeVisitor
seenPositive.insert(ty); seenPositive.insert(ty);
return false; return false;
} }
case Negative: case Polarity::Negative:
{ {
if (seenNegative.contains(ty)) if (seenNegative.contains(ty))
return true; return true;
@ -681,7 +703,7 @@ struct FreeTypeSearcher : TypeVisitor
seenNegative.insert(ty); seenNegative.insert(ty);
return false; return false;
} }
case Both: case Polarity::Mixed:
{ {
if (seenPositive.contains(ty) && seenNegative.contains(ty)) if (seenPositive.contains(ty) && seenNegative.contains(ty))
return true; return true;
@ -690,6 +712,8 @@ struct FreeTypeSearcher : TypeVisitor
seenNegative.insert(ty); seenNegative.insert(ty);
return false; return false;
} }
default:
LUAU_ASSERT(!"Unreachable");
} }
return false; return false;
@ -703,7 +727,7 @@ struct FreeTypeSearcher : TypeVisitor
bool visit(TypeId ty) override bool visit(TypeId ty) override
{ {
if (seenWithPolarity(ty)) if (seenWithCurrentPolarity(ty))
return false; return false;
LUAU_ASSERT(ty); LUAU_ASSERT(ty);
@ -712,7 +736,7 @@ struct FreeTypeSearcher : TypeVisitor
bool visit(TypeId ty, const FreeType& ft) override bool visit(TypeId ty, const FreeType& ft) override
{ {
if (seenWithPolarity(ty)) if (seenWithCurrentPolarity(ty))
return false; return false;
if (!subsumes(scope, ft.scope)) if (!subsumes(scope, ft.scope))
@ -720,16 +744,18 @@ struct FreeTypeSearcher : TypeVisitor
switch (polarity) switch (polarity)
{ {
case Positive: case Polarity::Positive:
positiveTypes[ty]++; positiveTypes[ty]++;
break; break;
case Negative: case Polarity::Negative:
negativeTypes[ty]++; negativeTypes[ty]++;
break; break;
case Both: case Polarity::Mixed:
positiveTypes[ty]++; positiveTypes[ty]++;
negativeTypes[ty]++; negativeTypes[ty]++;
break; break;
default:
LUAU_ASSERT(!"Unreachable");
} }
return true; return true;
@ -737,23 +763,25 @@ struct FreeTypeSearcher : TypeVisitor
bool visit(TypeId ty, const TableType& tt) override bool visit(TypeId ty, const TableType& tt) override
{ {
if (seenWithPolarity(ty)) if (seenWithCurrentPolarity(ty))
return false; return false;
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))
{ {
switch (polarity) switch (polarity)
{ {
case Positive: case Polarity::Positive:
positiveTypes[ty]++; positiveTypes[ty]++;
break; break;
case Negative: case Polarity::Negative:
negativeTypes[ty]++; negativeTypes[ty]++;
break; break;
case Both: case Polarity::Mixed:
positiveTypes[ty]++; positiveTypes[ty]++;
negativeTypes[ty]++; negativeTypes[ty]++;
break; break;
default:
LUAU_ASSERT(!"Unreachable");
} }
} }
@ -766,7 +794,7 @@ struct FreeTypeSearcher : TypeVisitor
LUAU_ASSERT(prop.isShared()); LUAU_ASSERT(prop.isShared());
Polarity p = polarity; Polarity p = polarity;
polarity = Both; polarity = Polarity::Mixed;
traverse(prop.type()); traverse(prop.type());
polarity = p; polarity = p;
} }
@ -783,7 +811,7 @@ struct FreeTypeSearcher : TypeVisitor
bool visit(TypeId ty, const FunctionType& ft) override bool visit(TypeId ty, const FunctionType& ft) override
{ {
if (seenWithPolarity(ty)) if (seenWithCurrentPolarity(ty))
return false; return false;
flip(); flip();
@ -802,7 +830,7 @@ struct FreeTypeSearcher : TypeVisitor
bool visit(TypePackId tp, const FreeTypePack& ftp) override bool visit(TypePackId tp, const FreeTypePack& ftp) override
{ {
if (seenWithPolarity(tp)) if (seenWithCurrentPolarity(tp))
return false; return false;
if (!subsumes(scope, ftp.scope)) if (!subsumes(scope, ftp.scope))
@ -810,16 +838,18 @@ struct FreeTypeSearcher : TypeVisitor
switch (polarity) switch (polarity)
{ {
case Positive: case Polarity::Positive:
positiveTypes[tp]++; positiveTypes[tp]++;
break; break;
case Negative: case Polarity::Negative:
negativeTypes[tp]++; negativeTypes[tp]++;
break; break;
case Both: case Polarity::Mixed:
positiveTypes[tp]++; positiveTypes[tp]++;
negativeTypes[tp]++; negativeTypes[tp]++;
break; break;
default:
LUAU_ASSERT(!"Unreachable");
} }
return true; return true;
@ -925,4 +955,23 @@ OccursCheckResult Unifier2::occursCheck(DenseHashSet<TypePackId>& seen, TypePack
return OccursCheckResult::Pass; return OccursCheckResult::Pass;
} }
TypeId Unifier2::freshType(NotNull<Scope> scope, Polarity polarity)
{
TypeId result = ::Luau::freshType(arena, builtinTypes, scope.get(), polarity);
newFreshTypes.emplace_back(result);
return result;
}
TypePackId Unifier2::freshTypePack(NotNull<Scope> scope, Polarity polarity)
{
TypePackId result = arena->freshTypePack(scope.get());
auto ftp = getMutable<FreeTypePack>(result);
LUAU_ASSERT(ftp);
ftp->polarity = polarity;
newFreshTypePacks.emplace_back(result);
return result;
}
} // namespace Luau } // namespace Luau

View file

@ -194,6 +194,7 @@ public:
{ {
Checked, Checked,
Native, Native,
Deprecated,
}; };
AstAttr(const Location& location, Type type); AstAttr(const Location& location, Type type);
@ -453,6 +454,7 @@ public:
void visit(AstVisitor* visitor) override; void visit(AstVisitor* visitor) override;
bool hasNativeAttribute() const; bool hasNativeAttribute() const;
bool hasAttribute(AstAttr::Type attributeType) const;
AstArray<AstAttr*> attributes; AstArray<AstAttr*> attributes;
AstArray<AstGenericType*> generics; AstArray<AstGenericType*> generics;
@ -890,14 +892,22 @@ class AstStatTypeFunction : public AstStat
public: public:
LUAU_RTTI(AstStatTypeFunction); LUAU_RTTI(AstStatTypeFunction);
AstStatTypeFunction(const Location& location, const AstName& name, const Location& nameLocation, AstExprFunction* body, bool exported); AstStatTypeFunction(
const Location& location,
const AstName& name,
const Location& nameLocation,
AstExprFunction* body,
bool exported,
bool hasErrors
);
void visit(AstVisitor* visitor) override; void visit(AstVisitor* visitor) override;
AstName name; AstName name;
Location nameLocation; Location nameLocation;
AstExprFunction* body; AstExprFunction* body = nullptr;
bool exported; bool exported = false;
bool hasErrors = false;
}; };
class AstStatDeclareGlobal : public AstStat class AstStatDeclareGlobal : public AstStat
@ -950,6 +960,7 @@ public:
void visit(AstVisitor* visitor) override; void visit(AstVisitor* visitor) override;
bool isCheckedFunction() const; bool isCheckedFunction() const;
bool hasAttribute(AstAttr::Type attributeType) const;
AstArray<AstAttr*> attributes; AstArray<AstAttr*> attributes;
AstName name; AstName name;
@ -1106,6 +1117,7 @@ public:
void visit(AstVisitor* visitor) override; void visit(AstVisitor* visitor) override;
bool isCheckedFunction() const; bool isCheckedFunction() const;
bool hasAttribute(AstAttr::Type attributeType) const;
AstArray<AstAttr*> attributes; AstArray<AstAttr*> attributes;
AstArray<AstGenericType*> generics; AstArray<AstGenericType*> generics;
@ -1127,6 +1139,16 @@ public:
AstExpr* expr; AstExpr* expr;
}; };
class AstTypeOptional : public AstType
{
public:
LUAU_RTTI(AstTypeOptional)
AstTypeOptional(const Location& location);
void visit(AstVisitor* visitor) override;
};
class AstTypeUnion : public AstType class AstTypeUnion : public AstType
{ {
public: public:
@ -1449,6 +1471,10 @@ public:
{ {
return visit(static_cast<AstStat*>(node)); return visit(static_cast<AstStat*>(node));
} }
virtual bool visit(class AstStatTypeFunction* node)
{
return visit(static_cast<AstStat*>(node));
}
virtual bool visit(class AstStatDeclareFunction* node) virtual bool visit(class AstStatDeclareFunction* node)
{ {
return visit(static_cast<AstStat*>(node)); return visit(static_cast<AstStat*>(node));
@ -1488,6 +1514,10 @@ public:
{ {
return visit(static_cast<AstType*>(node)); return visit(static_cast<AstType*>(node));
} }
virtual bool visit(class AstTypeOptional* node)
{
return visit(static_cast<AstType*>(node));
}
virtual bool visit(class AstTypeUnion* node) virtual bool visit(class AstTypeUnion* node)
{ {
return visit(static_cast<AstType*>(node)); return visit(static_cast<AstType*>(node));

View file

@ -105,6 +105,21 @@ public:
Position closeBracketPosition; Position closeBracketPosition;
}; };
class CstExprFunction : public CstNode
{
public:
LUAU_CST_RTTI(CstExprFunction)
CstExprFunction();
Position functionKeywordPosition{0, 0};
Position openGenericsPosition{0, 0};
AstArray<Position> genericsCommaPositions;
Position closeGenericsPosition{0, 0};
AstArray<Position> argsCommaPositions;
Position returnSpecifierPosition{0, 0};
};
class CstExprTable : public CstNode class CstExprTable : public CstNode
{ {
public: public:
@ -260,13 +275,24 @@ public:
Position opPosition; Position opPosition;
}; };
class CstStatFunction : public CstNode
{
public:
LUAU_CST_RTTI(CstStatFunction)
explicit CstStatFunction(Position functionKeywordPosition);
Position functionKeywordPosition;
};
class CstStatLocalFunction : public CstNode class CstStatLocalFunction : public CstNode
{ {
public: public:
LUAU_CST_RTTI(CstStatLocalFunction) LUAU_CST_RTTI(CstStatLocalFunction)
explicit CstStatLocalFunction(Position functionKeywordPosition); explicit CstStatLocalFunction(Position localKeywordPosition, Position functionKeywordPosition);
Position localKeywordPosition;
Position functionKeywordPosition; Position functionKeywordPosition;
}; };
@ -311,6 +337,17 @@ public:
Position equalsPosition; Position equalsPosition;
}; };
class CstStatTypeFunction : public CstNode
{
public:
LUAU_CST_RTTI(CstStatTypeFunction)
CstStatTypeFunction(Position typeKeywordPosition, Position functionKeywordPosition);
Position typeKeywordPosition;
Position functionKeywordPosition;
};
class CstTypeReference : public CstNode class CstTypeReference : public CstNode
{ {
public: public:
@ -359,6 +396,32 @@ public:
bool isArray = false; bool isArray = false;
}; };
class CstTypeFunction : public CstNode
{
public:
LUAU_CST_RTTI(CstTypeFunction)
CstTypeFunction(
Position openGenericsPosition,
AstArray<Position> genericsCommaPositions,
Position closeGenericsPosition,
Position openArgsPosition,
AstArray<std::optional<Position>> argumentNameColonPositions,
AstArray<Position> argumentsCommaPositions,
Position closeArgsPosition,
Position returnArrowPosition
);
Position openGenericsPosition;
AstArray<Position> genericsCommaPositions;
Position closeGenericsPosition;
Position openArgsPosition;
AstArray<std::optional<Position>> argumentNameColonPositions;
AstArray<Position> argumentsCommaPositions;
Position closeArgsPosition;
Position returnArrowPosition;
};
class CstTypeTypeof : public CstNode class CstTypeTypeof : public CstNode
{ {
public: public:
@ -370,6 +433,28 @@ public:
Position closePosition; Position closePosition;
}; };
class CstTypeUnion : public CstNode
{
public:
LUAU_CST_RTTI(CstTypeUnion)
CstTypeUnion(std::optional<Position> leadingPosition, AstArray<Position> separatorPositions);
std::optional<Position> leadingPosition;
AstArray<Position> separatorPositions;
};
class CstTypeIntersection : public CstNode
{
public:
LUAU_CST_RTTI(CstTypeIntersection)
explicit CstTypeIntersection(std::optional<Position> leadingPosition, AstArray<Position> separatorPositions);
std::optional<Position> leadingPosition;
AstArray<Position> separatorPositions;
};
class CstTypeSingletonString : public CstNode class CstTypeSingletonString : public CstNode
{ {
public: public:
@ -382,4 +467,26 @@ public:
unsigned int blockDepth; unsigned int blockDepth;
}; };
class CstTypePackExplicit : public CstNode
{
public:
LUAU_CST_RTTI(CstTypePackExplicit)
CstTypePackExplicit(Position openParenthesesPosition, Position closeParenthesesPosition, AstArray<Position> commaPositions);
Position openParenthesesPosition;
Position closeParenthesesPosition;
AstArray<Position> commaPositions;
};
class CstTypePackGeneric : public CstNode
{
public:
LUAU_CST_RTTI(CstTypePackGeneric)
explicit CstTypePackGeneric(Position ellipsisPosition);
Position ellipsisPosition;
};
} // namespace Luau } // namespace Luau

View file

@ -125,7 +125,7 @@ private:
AstStat* parseFor(); AstStat* parseFor();
// funcname ::= Name {`.' Name} [`:' Name] // funcname ::= Name {`.' Name} [`:' Name]
AstExpr* parseFunctionName(Location start_DEPRECATED, bool& hasself, AstName& debugname); AstExpr* parseFunctionName(bool& hasself, AstName& debugname);
// function funcname funcbody // function funcname funcbody
LUAU_FORCEINLINE AstStat* parseFunctionStat(const AstArray<AstAttr*>& attributes = {nullptr, 0}); LUAU_FORCEINLINE AstStat* parseFunctionStat(const AstArray<AstAttr*>& attributes = {nullptr, 0});
@ -155,9 +155,11 @@ private:
AstStat* parseTypeAlias(const Location& start, bool exported, Position typeKeywordPosition); AstStat* parseTypeAlias(const Location& start, bool exported, Position typeKeywordPosition);
// type function Name ... end // type function Name ... end
AstStat* parseTypeFunction(const Location& start, bool exported); AstStat* parseTypeFunction(const Location& start, bool exported, Position typeKeywordPosition);
AstDeclaredClassProp parseDeclaredClassMethod(const AstArray<AstAttr*>& attributes);
AstDeclaredClassProp parseDeclaredClassMethod_DEPRECATED();
AstDeclaredClassProp parseDeclaredClassMethod();
// `declare global' Name: Type | // `declare global' Name: Type |
// `declare function' Name`(' [parlist] `)' [`:` Type] // `declare function' Name`(' [parlist] `)' [`:` Type]
@ -192,7 +194,8 @@ private:
std::tuple<bool, Location, AstTypePack*> parseBindingList( std::tuple<bool, Location, AstTypePack*> parseBindingList(
TempVector<Binding>& result, TempVector<Binding>& result,
bool allowDot3 = false, bool allowDot3 = false,
TempVector<Position>* commaPositions = nullptr AstArray<Position>* commaPositions = nullptr,
std::optional<Position> initialCommaPosition = std::nullopt
); );
AstType* parseOptionalType(); AstType* parseOptionalType();
@ -209,9 +212,14 @@ private:
// | `(' [TypeList] `)' `->` ReturnType // | `(' [TypeList] `)' `->` ReturnType
// Returns the variadic annotation, if it exists. // Returns the variadic annotation, if it exists.
AstTypePack* parseTypeList(TempVector<AstType*>& result, TempVector<std::optional<AstArgumentName>>& resultNames); AstTypePack* parseTypeList(
TempVector<AstType*>& result,
TempVector<std::optional<AstArgumentName>>& resultNames,
TempVector<Position>* commaPositions = nullptr,
TempVector<std::optional<Position>>* nameColonPositions = nullptr
);
std::optional<AstTypeList> parseOptionalReturnType(); std::optional<AstTypeList> parseOptionalReturnType(Position* returnSpecifierPosition = nullptr);
std::pair<Location, AstTypeList> parseReturnType(); std::pair<Location, AstTypeList> parseReturnType();
struct TableIndexerResult struct TableIndexerResult
@ -222,9 +230,9 @@ private:
Position colonPosition; Position colonPosition;
}; };
TableIndexerResult parseTableIndexer(AstTableAccess access, std::optional<Location> accessLocation); TableIndexerResult parseTableIndexer(AstTableAccess access, std::optional<Location> accessLocation, Lexeme begin);
// Remove with FFlagLuauStoreCSTData // Remove with FFlagLuauStoreCSTData2
AstTableIndexer* parseTableIndexer_DEPRECATED(AstTableAccess access, std::optional<Location> accessLocation); AstTableIndexer* parseTableIndexer_DEPRECATED(AstTableAccess access, std::optional<Location> accessLocation, Lexeme begin);
AstTypeOrPack parseFunctionType(bool allowPack, const AstArray<AstAttr*>& attributes); AstTypeOrPack parseFunctionType(bool allowPack, const AstArray<AstAttr*>& attributes);
AstType* parseFunctionTypeTail( AstType* parseFunctionTypeTail(
@ -305,7 +313,7 @@ private:
std::pair<AstArray<AstGenericType*>, AstArray<AstGenericTypePack*>> parseGenericTypeList( std::pair<AstArray<AstGenericType*>, AstArray<AstGenericTypePack*>> parseGenericTypeList(
bool withDefaultValues, bool withDefaultValues,
Position* openPosition = nullptr, Position* openPosition = nullptr,
TempVector<Position>* commaPositions = nullptr, AstArray<Position>* commaPositions = nullptr,
Position* closePosition = nullptr Position* closePosition = nullptr
); );
@ -491,6 +499,7 @@ private:
std::vector<AstGenericTypePack*> scratchGenericTypePacks; std::vector<AstGenericTypePack*> scratchGenericTypePacks;
std::vector<std::optional<AstArgumentName>> scratchOptArgName; std::vector<std::optional<AstArgumentName>> scratchOptArgName;
std::vector<Position> scratchPosition; std::vector<Position> scratchPosition;
std::vector<std::optional<Position>> scratchOptPosition;
std::string scratchData; std::string scratchData;
CstNodeMap cstNodeMap; CstNodeMap cstNodeMap;

View file

@ -3,9 +3,24 @@
#include "Luau/Common.h" #include "Luau/Common.h"
LUAU_FASTFLAG(LuauDeprecatedAttribute);
namespace Luau namespace Luau
{ {
static bool hasAttributeInArray(const AstArray<AstAttr*> attributes, AstAttr::Type attributeType)
{
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
for (const auto attribute : attributes)
{
if (attribute->type == attributeType)
return true;
}
return false;
}
static void visitTypeList(AstVisitor* visitor, const AstTypeList& list) static void visitTypeList(AstVisitor* visitor, const AstTypeList& list)
{ {
for (AstType* ty : list.types) for (AstType* ty : list.types)
@ -277,6 +292,13 @@ bool AstExprFunction::hasNativeAttribute() const
return false; return false;
} }
bool AstExprFunction::hasAttribute(const AstAttr::Type attributeType) const
{
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
return hasAttributeInArray(attributes, attributeType);
}
AstExprTable::AstExprTable(const Location& location, const AstArray<Item>& items) AstExprTable::AstExprTable(const Location& location, const AstArray<Item>& items)
: AstExpr(ClassIndex(), location) : AstExpr(ClassIndex(), location)
, items(items) , items(items)
@ -791,13 +813,15 @@ AstStatTypeFunction::AstStatTypeFunction(
const AstName& name, const AstName& name,
const Location& nameLocation, const Location& nameLocation,
AstExprFunction* body, AstExprFunction* body,
bool exported bool exported,
bool hasErrors
) )
: AstStat(ClassIndex(), location) : AstStat(ClassIndex(), location)
, name(name) , name(name)
, nameLocation(nameLocation) , nameLocation(nameLocation)
, body(body) , body(body)
, exported(exported) , exported(exported)
, hasErrors(hasErrors)
{ {
} }
@ -894,6 +918,13 @@ bool AstStatDeclareFunction::isCheckedFunction() const
return false; return false;
} }
bool AstStatDeclareFunction::hasAttribute(AstAttr::Type attributeType) const
{
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
return hasAttributeInArray(attributes, attributeType);
}
AstStatDeclareClass::AstStatDeclareClass( AstStatDeclareClass::AstStatDeclareClass(
const Location& location, const Location& location,
const AstName& name, const AstName& name,
@ -1057,6 +1088,13 @@ bool AstTypeFunction::isCheckedFunction() const
return false; return false;
} }
bool AstTypeFunction::hasAttribute(AstAttr::Type attributeType) const
{
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
return hasAttributeInArray(attributes, attributeType);
}
AstTypeTypeof::AstTypeTypeof(const Location& location, AstExpr* expr) AstTypeTypeof::AstTypeTypeof(const Location& location, AstExpr* expr)
: AstType(ClassIndex(), location) : AstType(ClassIndex(), location)
, expr(expr) , expr(expr)
@ -1069,6 +1107,16 @@ void AstTypeTypeof::visit(AstVisitor* visitor)
expr->visit(visitor); expr->visit(visitor);
} }
AstTypeOptional::AstTypeOptional(const Location& location)
: AstType(ClassIndex(), location)
{
}
void AstTypeOptional::visit(AstVisitor* visitor)
{
visitor->visit(this);
}
AstTypeUnion::AstTypeUnion(const Location& location, const AstArray<AstType*>& types) AstTypeUnion::AstTypeUnion(const Location& location, const AstArray<AstType*>& types)
: AstType(ClassIndex(), location) : AstType(ClassIndex(), location)
, types(types) , types(types)

View file

@ -38,6 +38,11 @@ CstExprIndexExpr::CstExprIndexExpr(Position openBracketPosition, Position closeB
{ {
} }
CstExprFunction::CstExprFunction()
: CstNode(CstClassIndex())
{
}
CstExprTable::CstExprTable(const AstArray<Item>& items) CstExprTable::CstExprTable(const AstArray<Item>& items)
: CstNode(CstClassIndex()) : CstNode(CstClassIndex())
, items(items) , items(items)
@ -125,12 +130,19 @@ CstStatCompoundAssign::CstStatCompoundAssign(Position opPosition)
{ {
} }
CstStatLocalFunction::CstStatLocalFunction(Position functionKeywordPosition) CstStatFunction::CstStatFunction(Position functionKeywordPosition)
: CstNode(CstClassIndex()) : CstNode(CstClassIndex())
, functionKeywordPosition(functionKeywordPosition) , functionKeywordPosition(functionKeywordPosition)
{ {
} }
CstStatLocalFunction::CstStatLocalFunction(Position localKeywordPosition, Position functionKeywordPosition)
: CstNode(CstClassIndex())
, localKeywordPosition(localKeywordPosition)
, functionKeywordPosition(functionKeywordPosition)
{
}
CstGenericType::CstGenericType(std::optional<Position> defaultEqualsPosition) CstGenericType::CstGenericType(std::optional<Position> defaultEqualsPosition)
: CstNode(CstClassIndex()) : CstNode(CstClassIndex())
, defaultEqualsPosition(defaultEqualsPosition) , defaultEqualsPosition(defaultEqualsPosition)
@ -160,6 +172,13 @@ CstStatTypeAlias::CstStatTypeAlias(
{ {
} }
CstStatTypeFunction::CstStatTypeFunction(Position typeKeywordPosition, Position functionKeywordPosition)
: CstNode(CstClassIndex())
, typeKeywordPosition(typeKeywordPosition)
, functionKeywordPosition(functionKeywordPosition)
{
}
CstTypeReference::CstTypeReference( CstTypeReference::CstTypeReference(
std::optional<Position> prefixPointPosition, std::optional<Position> prefixPointPosition,
Position openParametersPosition, Position openParametersPosition,
@ -181,6 +200,28 @@ CstTypeTable::CstTypeTable(AstArray<Item> items, bool isArray)
{ {
} }
CstTypeFunction::CstTypeFunction(
Position openGenericsPosition,
AstArray<Position> genericsCommaPositions,
Position closeGenericsPosition,
Position openArgsPosition,
AstArray<std::optional<Position>> argumentNameColonPositions,
AstArray<Position> argumentsCommaPositions,
Position closeArgsPosition,
Position returnArrowPosition
)
: CstNode(CstClassIndex())
, openGenericsPosition(openGenericsPosition)
, genericsCommaPositions(genericsCommaPositions)
, closeGenericsPosition(closeGenericsPosition)
, openArgsPosition(openArgsPosition)
, argumentNameColonPositions(argumentNameColonPositions)
, argumentsCommaPositions(argumentsCommaPositions)
, closeArgsPosition(closeArgsPosition)
, returnArrowPosition(returnArrowPosition)
{
}
CstTypeTypeof::CstTypeTypeof(Position openPosition, Position closePosition) CstTypeTypeof::CstTypeTypeof(Position openPosition, Position closePosition)
: CstNode(CstClassIndex()) : CstNode(CstClassIndex())
, openPosition(openPosition) , openPosition(openPosition)
@ -188,6 +229,20 @@ CstTypeTypeof::CstTypeTypeof(Position openPosition, Position closePosition)
{ {
} }
CstTypeUnion::CstTypeUnion(std::optional<Position> leadingPosition, AstArray<Position> separatorPositions)
: CstNode(CstClassIndex())
, leadingPosition(leadingPosition)
, separatorPositions(separatorPositions)
{
}
CstTypeIntersection::CstTypeIntersection(std::optional<Position> leadingPosition, AstArray<Position> separatorPositions)
: CstNode(CstClassIndex())
, leadingPosition(leadingPosition)
, separatorPositions(separatorPositions)
{
}
CstTypeSingletonString::CstTypeSingletonString(AstArray<char> sourceString, CstExprConstantString::QuoteStyle quoteStyle, unsigned int blockDepth) CstTypeSingletonString::CstTypeSingletonString(AstArray<char> sourceString, CstExprConstantString::QuoteStyle quoteStyle, unsigned int blockDepth)
: CstNode(CstClassIndex()) : CstNode(CstClassIndex())
, sourceString(sourceString) , sourceString(sourceString)
@ -197,4 +252,18 @@ CstTypeSingletonString::CstTypeSingletonString(AstArray<char> sourceString, CstE
LUAU_ASSERT(quoteStyle != CstExprConstantString::QuotedInterp); LUAU_ASSERT(quoteStyle != CstExprConstantString::QuotedInterp);
} }
CstTypePackExplicit::CstTypePackExplicit(Position openParenthesesPosition, Position closeParenthesesPosition, AstArray<Position> commaPositions)
: CstNode(CstClassIndex())
, openParenthesesPosition(openParenthesesPosition)
, closeParenthesesPosition(closeParenthesesPosition)
, commaPositions(commaPositions)
{
}
CstTypePackGeneric::CstTypePackGeneric(Position ellipsisPosition)
: CstNode(CstClassIndex())
, ellipsisPosition(ellipsisPosition)
{
}
} // namespace Luau } // namespace Luau

View file

@ -8,9 +8,6 @@
#include <limits.h> #include <limits.h>
LUAU_FASTFLAGVARIABLE(LexerResumesFromPosition2)
LUAU_FASTFLAGVARIABLE(LexerFixInterpStringStart)
namespace Luau namespace Luau
{ {
@ -342,12 +339,9 @@ Lexer::Lexer(const char* buffer, size_t bufferSize, AstNameTable& names, Positio
: buffer(buffer) : buffer(buffer)
, bufferSize(bufferSize) , bufferSize(bufferSize)
, offset(0) , offset(0)
, line(FFlag::LexerResumesFromPosition2 ? startPosition.line : 0) , line(startPosition.line)
, lineOffset(FFlag::LexerResumesFromPosition2 ? 0u - startPosition.column : 0) , lineOffset(0u - startPosition.column)
, lexeme( , lexeme((Location(Position(startPosition.line, startPosition.column), 0)), Lexeme::Eof)
(FFlag::LexerResumesFromPosition2 ? Location(Position(startPosition.line, startPosition.column), 0) : Location(Position(0, 0), 0)),
Lexeme::Eof
)
, names(names) , names(names)
, skipComments(false) , skipComments(false)
, readNames(true) , readNames(true)
@ -793,7 +787,7 @@ Lexeme Lexer::readNext()
return Lexeme(Location(start, 1), '}'); return Lexeme(Location(start, 1), '}');
} }
return readInterpolatedStringSection(FFlag::LexerFixInterpStringStart ? start : position(), Lexeme::InterpStringMid, Lexeme::InterpStringEnd); return readInterpolatedStringSection(start, Lexeme::InterpStringMid, Lexeme::InterpStringEnd);
} }
case '=': case '=':

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,35 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "Luau/RequireNavigator.h"
#include "Luau/RequirerUtils.h"
struct FileNavigationContext : Luau::Require::NavigationContext
{
using NavigateResult = Luau::Require::NavigationContext::NavigateResult;
FileNavigationContext(std::string requirerPath);
std::string getRequirerIdentifier() const override;
// Navigation interface
NavigateResult reset(const std::string& requirerChunkname) override;
NavigateResult jumpToAlias(const std::string& path) override;
NavigateResult toParent() override;
NavigateResult toChild(const std::string& component) override;
bool isConfigPresent() const override;
std::optional<std::string> getConfig() const override;
// Custom capabilities
bool isModulePresent() const;
std::optional<std::string> getIdentifier() const;
std::string path;
std::string suffix;
std::string requirerPath;
private:
NavigateResult storePathResult(PathResult result);
};

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/Require.h"
#include "Luau/Compiler.h"
#include "lua.h"
#include <functional>
#include <string>
void requireConfigInit(luarequire_Configuration* config);
struct ReplRequirer
{
ReplRequirer(
std::function<Luau::CompileOptions()> copts,
std::function<bool()> coverageActive,
std::function<bool()> codegenEnabled,
std::function<void(lua_State*, int)> coverageTrack
);
std::function<Luau::CompileOptions()> copts;
std::function<bool()> coverageActive;
std::function<bool()> codegenEnabled;
std::function<void(lua_State*, int)> coverageTrack;
std::string absPath;
std::string relPath;
std::string suffix;
};

View file

@ -1,84 +0,0 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "Luau/Config.h"
#include <functional>
#include <string>
#include <string_view>
class RequireResolver
{
public:
enum class ModuleStatus
{
Cached,
FileRead,
ErrorReported
};
struct ResolvedRequire
{
ModuleStatus status;
std::string identifier;
std::string absolutePath;
std::string sourceCode;
};
struct RequireContext
{
virtual ~RequireContext() = default;
virtual std::string getPath() = 0;
virtual bool isRequireAllowed() = 0;
virtual bool isStdin() = 0;
virtual std::string createNewIdentifer(const std::string& path) = 0;
};
struct CacheManager
{
virtual ~CacheManager() = default;
virtual bool isCached(const std::string& path)
{
return false;
}
};
struct ErrorHandler
{
virtual ~ErrorHandler() = default;
virtual void reportError(const std::string message) {}
};
RequireResolver(std::string pathToResolve, RequireContext& requireContext, CacheManager& cacheManager, ErrorHandler& errorHandler);
[[nodiscard]] ResolvedRequire resolveRequire(std::function<void(const ModuleStatus)> completionCallback = nullptr);
private:
std::string pathToResolve;
RequireContext& requireContext;
CacheManager& cacheManager;
ErrorHandler& errorHandler;
ResolvedRequire resolvedRequire;
bool isRequireResolved = false;
Luau::Config config;
std::string lastSearchedDir;
bool isConfigFullyResolved = false;
[[nodiscard]] bool initialize();
ModuleStatus findModule();
ModuleStatus findModuleImpl();
[[nodiscard]] bool resolveAndStoreDefaultPaths();
std::optional<std::string> getRequiringContextAbsolute();
std::string getRequiringContextRelative();
[[nodiscard]] bool substituteAliasIfPresent(std::string& path);
std::optional<std::string> getAlias(std::string alias);
[[nodiscard]] bool parseNextConfig();
[[nodiscard]] bool parseConfigInDirectory(const std::string& directory);
};

View file

@ -0,0 +1,36 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include <optional>
#include <string>
#include <string_view>
struct PathResult
{
enum class Status
{
SUCCESS,
AMBIGUOUS,
NOT_FOUND
};
Status status;
std::string absPath;
std::string relPath;
std::string suffix;
};
PathResult getStdInResult();
PathResult getAbsolutePathResult(const std::string& path);
// If given an absolute path, this will implicitly call getAbsolutePathResult.
// Aliases prevent us from solely operating on relative paths, so we need to
// be able to fall back to operating on absolute paths if needed.
PathResult tryGetRelativePathResult(const std::string& path);
PathResult getParent(const std::string& absPath, const std::string& relPath);
PathResult getChild(const std::string& absPath, const std::string& relPath, const std::string& name);
bool isFilePresent(const std::string& path, const std::string& suffix);
std::optional<std::string> getFileContents(const std::string& path, const std::string& suffix);

View file

@ -7,9 +7,10 @@
#include "Luau/TypeAttach.h" #include "Luau/TypeAttach.h"
#include "Luau/Transpiler.h" #include "Luau/Transpiler.h"
#include "Luau/AnalyzeRequirer.h"
#include "Luau/FileUtils.h" #include "Luau/FileUtils.h"
#include "Luau/Flags.h" #include "Luau/Flags.h"
#include "Luau/Require.h" #include "Luau/RequireNavigator.h"
#include <condition_variable> #include <condition_variable>
#include <functional> #include <functional>
@ -173,15 +174,18 @@ struct CliFileResolver : Luau::FileResolver
{ {
std::string path{expr->value.data, expr->value.size}; std::string path{expr->value.data, expr->value.size};
AnalysisRequireContext requireContext{context->name}; FileNavigationContext navigationContext{context->name};
AnalysisCacheManager cacheManager; Luau::Require::ErrorHandler nullErrorHandler{};
AnalysisErrorHandler errorHandler;
RequireResolver resolver(path, requireContext, cacheManager, errorHandler); Luau::Require::Navigator navigator(navigationContext, nullErrorHandler);
RequireResolver::ResolvedRequire resolvedRequire = resolver.resolveRequire(); if (navigator.navigate(path) != Luau::Require::Navigator::Status::Success)
return std::nullopt;
if (resolvedRequire.status == RequireResolver::ModuleStatus::FileRead) if (!navigationContext.isModulePresent())
return {{resolvedRequire.identifier}}; return std::nullopt;
if (std::optional<std::string> identifier = navigationContext.getIdentifier())
return {{*identifier}};
} }
return std::nullopt; return std::nullopt;
@ -193,48 +197,6 @@ struct CliFileResolver : Luau::FileResolver
return "stdin"; return "stdin";
return name; return name;
} }
private:
struct AnalysisRequireContext : RequireResolver::RequireContext
{
explicit AnalysisRequireContext(std::string path)
: path(std::move(path))
{
}
std::string getPath() override
{
return path;
}
bool isRequireAllowed() override
{
return true;
}
bool isStdin() override
{
return path == "-";
}
std::string createNewIdentifer(const std::string& path) override
{
return path;
}
private:
std::string path;
};
struct AnalysisCacheManager : public RequireResolver::CacheManager
{
AnalysisCacheManager() = default;
};
struct AnalysisErrorHandler : RequireResolver::ErrorHandler
{
AnalysisErrorHandler() = default;
};
}; };
struct CliConfigResolver : Luau::ConfigResolver struct CliConfigResolver : Luau::ConfigResolver

View file

@ -0,0 +1,99 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/AnalyzeRequirer.h"
#include "Luau/RequireNavigator.h"
#include "Luau/RequirerUtils.h"
#include <string>
#include <string_view>
Luau::Require::NavigationContext::NavigateResult FileNavigationContext::storePathResult(PathResult result)
{
if (result.status == PathResult::Status::AMBIGUOUS)
return Luau::Require::NavigationContext::NavigateResult::Ambiguous;
if (result.status == PathResult::Status::NOT_FOUND)
return Luau::Require::NavigationContext::NavigateResult::NotFound;
path = result.absPath;
suffix = result.suffix;
return Luau::Require::NavigationContext::NavigateResult::Success;
}
FileNavigationContext::FileNavigationContext(std::string requirerPath)
{
std::string_view path = requirerPath;
if (path.size() >= 10 && path.substr(path.size() - 10) == "/init.luau")
{
path.remove_suffix(10);
}
else if (path.size() >= 9 && path.substr(path.size() - 9) == "/init.lua")
{
path.remove_suffix(9);
}
else if (path.size() >= 5 && path.substr(path.size() - 5) == ".luau")
{
path.remove_suffix(5);
}
else if (path.size() >= 4 && path.substr(path.size() - 4) == ".lua")
{
path.remove_suffix(4);
}
this->requirerPath = path;
}
std::string FileNavigationContext::getRequirerIdentifier() const
{
return requirerPath;
}
Luau::Require::NavigationContext::NavigateResult FileNavigationContext::reset(const std::string& requirerChunkname)
{
if (requirerChunkname == "-")
{
return storePathResult(getStdInResult());
}
return storePathResult(tryGetRelativePathResult(requirerChunkname));
}
Luau::Require::NavigationContext::NavigateResult FileNavigationContext::jumpToAlias(const std::string& path)
{
Luau::Require::NavigationContext::NavigateResult result = storePathResult(getAbsolutePathResult(path));
if (result != Luau::Require::NavigationContext::NavigateResult::Success)
return result;
return Luau::Require::NavigationContext::NavigateResult::Success;
}
Luau::Require::NavigationContext::NavigateResult FileNavigationContext::toParent()
{
return storePathResult(getParent(path, path));
}
Luau::Require::NavigationContext::NavigateResult FileNavigationContext::toChild(const std::string& component)
{
return storePathResult(getChild(path, path, component));
}
bool FileNavigationContext::isModulePresent() const
{
return isFilePresent(path, suffix);
}
std::optional<std::string> FileNavigationContext::getIdentifier() const
{
return path + suffix;
}
bool FileNavigationContext::isConfigPresent() const
{
return isFilePresent(path, "/.luaurc");
}
std::optional<std::string> FileNavigationContext::getConfig() const
{
return getFileContents(path, "/.luaurc");
}

View file

@ -31,7 +31,7 @@ static void setLuauFlags(bool state)
void setLuauFlagsDefault() void setLuauFlagsDefault()
{ {
for (Luau::FValue<bool>* flag = Luau::FValue<bool>::list; flag; flag = flag->next) for (Luau::FValue<bool>* flag = Luau::FValue<bool>::list; flag; flag = flag->next)
if (strncmp(flag->name, "Luau", 4) == 0 && !Luau::isFlagExperimental(flag->name)) if (strncmp(flag->name, "Luau", 4) == 0 && !Luau::isAnalysisFlagExperimental(flag->name))
flag->value = true; flag->value = true;
} }

View file

@ -14,6 +14,7 @@
#include "Luau/FileUtils.h" #include "Luau/FileUtils.h"
#include "Luau/Flags.h" #include "Luau/Flags.h"
#include "Luau/Profiler.h" #include "Luau/Profiler.h"
#include "Luau/ReplRequirer.h"
#include "Luau/Require.h" #include "Luau/Require.h"
#include "isocline.h" #include "isocline.h"
@ -113,172 +114,6 @@ static int lua_loadstring(lua_State* L)
return 2; // return nil plus error message return 2; // return nil plus error message
} }
static int finishrequire(lua_State* L)
{
if (lua_isstring(L, -1))
lua_error(L);
return 1;
}
struct RuntimeRequireContext : public RequireResolver::RequireContext
{
// In the context of the REPL, source is the calling context's chunkname.
//
// These chunknames have certain prefixes that indicate context. These
// are used when displaying debug information (see luaO_chunkid).
//
// Generally, the '@' prefix is used for filepaths, and the '=' prefix is
// used for custom chunknames, such as =stdin.
explicit RuntimeRequireContext(std::string source)
: source(std::move(source))
{
}
std::string getPath() override
{
return source.substr(1);
}
bool isRequireAllowed() override
{
return isStdin() || (!source.empty() && source[0] == '@');
}
bool isStdin() override
{
return source == "=stdin";
}
std::string createNewIdentifer(const std::string& path) override
{
return "@" + path;
}
private:
std::string source;
};
struct RuntimeCacheManager : public RequireResolver::CacheManager
{
explicit RuntimeCacheManager(lua_State* L)
: L(L)
{
}
bool isCached(const std::string& path) override
{
luaL_findtable(L, LUA_REGISTRYINDEX, "_MODULES", 1);
lua_getfield(L, -1, path.c_str());
bool cached = !lua_isnil(L, -1);
lua_pop(L, 2);
if (cached)
cacheKey = path;
return cached;
}
std::string cacheKey;
private:
lua_State* L;
};
struct RuntimeErrorHandler : RequireResolver::ErrorHandler
{
explicit RuntimeErrorHandler(lua_State* L)
: L(L)
{
}
void reportError(const std::string message) override
{
luaL_errorL(L, "%s", message.c_str());
}
private:
lua_State* L;
};
static int lua_require(lua_State* L)
{
std::string name = luaL_checkstring(L, 1);
RequireResolver::ResolvedRequire resolvedRequire;
{
lua_Debug ar;
lua_getinfo(L, 1, "s", &ar);
RuntimeRequireContext requireContext{ar.source};
RuntimeCacheManager cacheManager{L};
RuntimeErrorHandler errorHandler{L};
RequireResolver resolver(std::move(name), requireContext, cacheManager, errorHandler);
resolvedRequire = resolver.resolveRequire(
[L, &cacheKey = cacheManager.cacheKey](const RequireResolver::ModuleStatus status)
{
lua_getfield(L, LUA_REGISTRYINDEX, "_MODULES");
if (status == RequireResolver::ModuleStatus::Cached)
lua_getfield(L, -1, cacheKey.c_str());
}
);
}
if (resolvedRequire.status == RequireResolver::ModuleStatus::Cached)
return finishrequire(L);
// module needs to run in a new thread, isolated from the rest
// note: we create ML on main thread so that it doesn't inherit environment of L
lua_State* GL = lua_mainthread(L);
lua_State* ML = lua_newthread(GL);
lua_xmove(GL, L, 1);
// new thread needs to have the globals sandboxed
luaL_sandboxthread(ML);
// now we can compile & run module on the new thread
std::string bytecode = Luau::compile(resolvedRequire.sourceCode, copts());
if (luau_load(ML, resolvedRequire.identifier.c_str(), bytecode.data(), bytecode.size(), 0) == 0)
{
if (codegen)
{
Luau::CodeGen::CompilationOptions nativeOptions;
Luau::CodeGen::compile(ML, -1, nativeOptions);
}
if (coverageActive())
coverageTrack(ML, -1);
int status = lua_resume(ML, L, 0);
if (status == 0)
{
if (lua_gettop(ML) == 0)
lua_pushstring(ML, "module must return a value");
else if (!lua_istable(ML, -1) && !lua_isfunction(ML, -1))
lua_pushstring(ML, "module must return a table or function");
}
else if (status == LUA_YIELD)
{
lua_pushstring(ML, "module can not yield");
}
else if (!lua_isstring(ML, -1))
{
lua_pushstring(ML, "unknown error while running module");
}
}
// there's now a return value on top of ML; L stack: _MODULES ML
lua_xmove(ML, L, 1);
lua_pushvalue(L, -1);
lua_setfield(L, -4, resolvedRequire.absolutePath.c_str());
// L stack: _MODULES ML result
return finishrequire(L);
}
static int lua_collectgarbage(lua_State* L) static int lua_collectgarbage(lua_State* L)
{ {
const char* option = luaL_optstring(L, 1, "collect"); const char* option = luaL_optstring(L, 1, "collect");
@ -329,6 +164,39 @@ static int lua_callgrind(lua_State* L)
} }
#endif #endif
static void* createCliRequireContext(lua_State* L)
{
void* ctx = lua_newuserdatadtor(
L,
sizeof(ReplRequirer),
[](void* ptr)
{
static_cast<ReplRequirer*>(ptr)->~ReplRequirer();
}
);
if (!ctx)
luaL_error(L, "unable to allocate ReplRequirer");
ctx = new (ctx) ReplRequirer{
copts,
coverageActive,
[]()
{
return codegen;
},
coverageTrack,
};
// Store ReplRequirer in the registry to keep it alive for the lifetime of
// this lua_State. Memory address is used as a key to avoid collisions.
lua_pushlightuserdata(L, ctx);
lua_insert(L, -2);
lua_settable(L, LUA_REGISTRYINDEX);
return ctx;
}
void setupState(lua_State* L) void setupState(lua_State* L)
{ {
if (codegen) if (codegen)
@ -338,7 +206,6 @@ void setupState(lua_State* L)
static const luaL_Reg funcs[] = { static const luaL_Reg funcs[] = {
{"loadstring", lua_loadstring}, {"loadstring", lua_loadstring},
{"require", lua_require},
{"collectgarbage", lua_collectgarbage}, {"collectgarbage", lua_collectgarbage},
#ifdef CALLGRIND #ifdef CALLGRIND
{"callgrind", lua_callgrind}, {"callgrind", lua_callgrind},
@ -350,6 +217,8 @@ void setupState(lua_State* L)
luaL_register(L, NULL, funcs); luaL_register(L, NULL, funcs);
lua_pop(L, 1); lua_pop(L, 1);
luaopen_require(L, requireConfigInit, createCliRequireContext(L));
luaL_sandbox(L); luaL_sandbox(L);
} }
@ -712,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;

221
CLI/src/ReplRequirer.cpp Normal file
View file

@ -0,0 +1,221 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/ReplRequirer.h"
#include "Luau/CodeGen.h"
#include "Luau/CodeGenOptions.h"
#include "Luau/Require.h"
#include "Luau/RequirerUtils.h"
#include "lua.h"
#include "lualib.h"
#include <string>
#include <string_view>
#include <utility>
static luarequire_WriteResult write(std::optional<std::string> contents, char* buffer, size_t bufferSize, size_t* sizeOut)
{
if (!contents)
return luarequire_WriteResult::WRITE_FAILURE;
size_t nullTerminatedSize = contents->size() + 1;
if (bufferSize < nullTerminatedSize)
{
*sizeOut = nullTerminatedSize;
return luarequire_WriteResult::WRITE_BUFFER_TOO_SMALL;
}
*sizeOut = nullTerminatedSize;
memcpy(buffer, contents->c_str(), nullTerminatedSize);
return luarequire_WriteResult::WRITE_SUCCESS;
}
static luarequire_NavigateResult storePathResult(ReplRequirer* req, PathResult result)
{
if (result.status == PathResult::Status::AMBIGUOUS)
return NAVIGATE_AMBIGUOUS;
if (result.status == PathResult::Status::NOT_FOUND)
return NAVIGATE_NOT_FOUND;
req->absPath = result.absPath;
req->relPath = result.relPath;
req->suffix = result.suffix;
return NAVIGATE_SUCCESS;
}
static bool is_require_allowed(lua_State* L, void* ctx, const char* requirer_chunkname)
{
std::string_view chunkname = requirer_chunkname;
return chunkname == "=stdin" || (!chunkname.empty() && chunkname[0] == '@');
}
static luarequire_NavigateResult reset(lua_State* L, void* ctx, const char* requirer_chunkname)
{
ReplRequirer* req = static_cast<ReplRequirer*>(ctx);
std::string chunkname = requirer_chunkname;
if (chunkname == "=stdin")
{
return storePathResult(req, getStdInResult());
}
else if (!chunkname.empty() && chunkname[0] == '@')
{
return storePathResult(req, tryGetRelativePathResult(chunkname.substr(1)));
}
return NAVIGATE_NOT_FOUND;
}
static luarequire_NavigateResult jump_to_alias(lua_State* L, void* ctx, const char* path)
{
ReplRequirer* req = static_cast<ReplRequirer*>(ctx);
luarequire_NavigateResult result = storePathResult(req, getAbsolutePathResult(path));
if (result != NAVIGATE_SUCCESS)
return result;
// Jumping to an absolute path breaks the relative-require chain. The best
// we can do is to store the absolute path itself.
req->relPath = req->absPath;
return NAVIGATE_SUCCESS;
}
static luarequire_NavigateResult to_parent(lua_State* L, void* ctx)
{
ReplRequirer* req = static_cast<ReplRequirer*>(ctx);
return storePathResult(req, getParent(req->absPath, req->relPath));
}
static luarequire_NavigateResult to_child(lua_State* L, void* ctx, const char* name)
{
ReplRequirer* req = static_cast<ReplRequirer*>(ctx);
return storePathResult(req, getChild(req->absPath, req->relPath, name));
}
static bool is_module_present(lua_State* L, void* ctx)
{
ReplRequirer* req = static_cast<ReplRequirer*>(ctx);
return isFilePresent(req->absPath, req->suffix);
}
static luarequire_WriteResult get_contents(lua_State* L, void* ctx, char* buffer, size_t buffer_size, size_t* size_out)
{
ReplRequirer* req = static_cast<ReplRequirer*>(ctx);
return write(getFileContents(req->absPath, req->suffix), buffer, buffer_size, size_out);
}
static luarequire_WriteResult get_chunkname(lua_State* L, void* ctx, char* buffer, size_t buffer_size, size_t* size_out)
{
ReplRequirer* req = static_cast<ReplRequirer*>(ctx);
return write("@" + req->relPath, buffer, buffer_size, size_out);
}
static luarequire_WriteResult get_cache_key(lua_State* L, void* ctx, char* buffer, size_t buffer_size, size_t* size_out)
{
ReplRequirer* req = static_cast<ReplRequirer*>(ctx);
return write(req->absPath + req->suffix, buffer, buffer_size, size_out);
}
static bool is_config_present(lua_State* L, void* ctx)
{
ReplRequirer* req = static_cast<ReplRequirer*>(ctx);
return isFilePresent(req->absPath, "/.luaurc");
}
static luarequire_WriteResult get_config(lua_State* L, void* ctx, char* buffer, size_t buffer_size, size_t* size_out)
{
ReplRequirer* req = static_cast<ReplRequirer*>(ctx);
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)
{
ReplRequirer* req = static_cast<ReplRequirer*>(ctx);
// module needs to run in a new thread, isolated from the rest
// note: we create ML on main thread so that it doesn't inherit environment of L
lua_State* GL = lua_mainthread(L);
lua_State* ML = lua_newthread(GL);
lua_xmove(GL, L, 1);
// new thread needs to have the globals sandboxed
luaL_sandboxthread(ML);
// now we can compile & run module on the new thread
std::string bytecode = Luau::compile(contents, req->copts());
if (luau_load(ML, chunkname, bytecode.data(), bytecode.size(), 0) == 0)
{
if (req->codegenEnabled())
{
Luau::CodeGen::CompilationOptions nativeOptions;
Luau::CodeGen::compile(ML, -1, nativeOptions);
}
if (req->coverageActive())
req->coverageTrack(ML, -1);
int status = lua_resume(ML, L, 0);
if (status == 0)
{
if (lua_gettop(ML) == 0)
lua_pushstring(ML, "module must return a value");
else if (!lua_istable(ML, -1) && !lua_isfunction(ML, -1))
lua_pushstring(ML, "module must return a table or function");
}
else if (status == LUA_YIELD)
{
lua_pushstring(ML, "module can not yield");
}
else if (!lua_isstring(ML, -1))
{
lua_pushstring(ML, "unknown error while running module");
}
}
// add ML result to L stack
lua_xmove(ML, L, 1);
if (lua_isstring(L, -1))
lua_error(L);
// remove ML thread from L stack
lua_remove(L, -2);
// added one value to L stack: module result
return 1;
}
void requireConfigInit(luarequire_Configuration* config)
{
if (config == nullptr)
return;
config->is_require_allowed = is_require_allowed;
config->reset = reset;
config->jump_to_alias = jump_to_alias;
config->to_parent = to_parent;
config->to_child = to_child;
config->is_module_present = is_module_present;
config->get_contents = get_contents;
config->is_config_present = is_config_present;
config->get_chunkname = get_chunkname;
config->get_cache_key = get_cache_key;
config->get_config = get_config;
config->load = load;
}
ReplRequirer::ReplRequirer(
std::function<Luau::CompileOptions()> copts,
std::function<bool()> coverageActive,
std::function<bool()> codegenEnabled,
std::function<void(lua_State*, int)> coverageTrack
)
: copts(std::move(copts))
, coverageActive(std::move(coverageActive))
, codegenEnabled(std::move(codegenEnabled))
, coverageTrack(std::move(coverageTrack))
{
}

View file

@ -1,313 +0,0 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/Require.h"
#include "Luau/FileUtils.h"
#include "Luau/Common.h"
#include "Luau/Config.h"
#include <algorithm>
#include <array>
#include <utility>
static constexpr char kRequireErrorGeneric[] = "error requiring module";
RequireResolver::RequireResolver(std::string path, RequireContext& requireContext, CacheManager& cacheManager, ErrorHandler& errorHandler)
: pathToResolve(std::move(path))
, requireContext(requireContext)
, cacheManager(cacheManager)
, errorHandler(errorHandler)
{
}
RequireResolver::ResolvedRequire RequireResolver::resolveRequire(std::function<void(const ModuleStatus)> completionCallback)
{
if (isRequireResolved)
{
errorHandler.reportError("require statement has already been resolved");
return ResolvedRequire{ModuleStatus::ErrorReported};
}
if (!initialize())
return ResolvedRequire{ModuleStatus::ErrorReported};
resolvedRequire.status = findModule();
if (completionCallback)
completionCallback(resolvedRequire.status);
isRequireResolved = true;
return resolvedRequire;
}
static bool hasValidPrefix(std::string_view path)
{
return path.compare(0, 2, "./") == 0 || path.compare(0, 3, "../") == 0 || path.compare(0, 1, "@") == 0;
}
static bool isPathAmbiguous(const std::string& path)
{
bool found = false;
for (const char* suffix : {".luau", ".lua"})
{
if (isFile(path + suffix))
{
if (found)
return true;
else
found = true;
}
}
if (isDirectory(path) && found)
return true;
return false;
}
bool RequireResolver::initialize()
{
if (!requireContext.isRequireAllowed())
{
errorHandler.reportError("require is not supported in this context");
return false;
}
if (isAbsolutePath(pathToResolve))
{
errorHandler.reportError("cannot require an absolute path");
return false;
}
std::replace(pathToResolve.begin(), pathToResolve.end(), '\\', '/');
if (!hasValidPrefix(pathToResolve))
{
errorHandler.reportError("require path must start with a valid prefix: ./, ../, or @");
return false;
}
return substituteAliasIfPresent(pathToResolve);
}
RequireResolver::ModuleStatus RequireResolver::findModule()
{
if (!resolveAndStoreDefaultPaths())
return ModuleStatus::ErrorReported;
if (isPathAmbiguous(resolvedRequire.absolutePath))
{
errorHandler.reportError("require path could not be resolved to a unique file");
return ModuleStatus::ErrorReported;
}
static constexpr std::array<const char*, 4> possibleSuffixes = {".luau", ".lua", "/init.luau", "/init.lua"};
size_t unsuffixedAbsolutePathSize = resolvedRequire.absolutePath.size();
for (const char* possibleSuffix : possibleSuffixes)
{
resolvedRequire.absolutePath += possibleSuffix;
if (cacheManager.isCached(resolvedRequire.absolutePath))
return ModuleStatus::Cached;
// Try to read the matching file
if (std::optional<std::string> source = readFile(resolvedRequire.absolutePath))
{
resolvedRequire.identifier = requireContext.createNewIdentifer(resolvedRequire.identifier + possibleSuffix);
resolvedRequire.sourceCode = *source;
return ModuleStatus::FileRead;
}
resolvedRequire.absolutePath.resize(unsuffixedAbsolutePathSize); // truncate to remove suffix
}
if (hasFileExtension(resolvedRequire.absolutePath, {".luau", ".lua"}) && isFile(resolvedRequire.absolutePath))
{
errorHandler.reportError("error requiring module: consider removing the file extension");
return ModuleStatus::ErrorReported;
}
errorHandler.reportError(kRequireErrorGeneric);
return ModuleStatus::ErrorReported;
}
bool RequireResolver::resolveAndStoreDefaultPaths()
{
if (!isAbsolutePath(pathToResolve))
{
std::string identifierContext = getRequiringContextRelative();
std::optional<std::string> absolutePathContext = getRequiringContextAbsolute();
if (!absolutePathContext)
return false;
// resolvePath automatically sanitizes/normalizes the paths
std::optional<std::string> identifier = resolvePath(pathToResolve, identifierContext);
std::optional<std::string> absolutePath = resolvePath(pathToResolve, *absolutePathContext);
if (!identifier || !absolutePath)
{
errorHandler.reportError("could not resolve require path");
return false;
}
resolvedRequire.identifier = std::move(*identifier);
resolvedRequire.absolutePath = std::move(*absolutePath);
}
else
{
// Here we must explicitly sanitize, as the path is taken as is
std::string sanitizedPath = normalizePath(pathToResolve);
resolvedRequire.identifier = sanitizedPath;
resolvedRequire.absolutePath = std::move(sanitizedPath);
}
return true;
}
std::optional<std::string> RequireResolver::getRequiringContextAbsolute()
{
std::string requiringFile;
if (isAbsolutePath(requireContext.getPath()))
{
// We already have an absolute path for the requiring file
requiringFile = requireContext.getPath();
}
else
{
// Requiring file's stored path is relative to the CWD, must make absolute
std::optional<std::string> cwd = getCurrentWorkingDirectory();
if (!cwd)
{
errorHandler.reportError("could not determine current working directory");
return std::nullopt;
}
if (requireContext.isStdin())
{
// Require statement is being executed from REPL input prompt
// The requiring context is the pseudo-file "stdin" in the CWD
requiringFile = joinPaths(*cwd, "stdin");
}
else
{
// Require statement is being executed in a file, must resolve relative to CWD
requiringFile = normalizePath(joinPaths(*cwd, requireContext.getPath()));
}
}
std::replace(requiringFile.begin(), requiringFile.end(), '\\', '/');
return requiringFile;
}
std::string RequireResolver::getRequiringContextRelative()
{
return requireContext.isStdin() ? "./" : requireContext.getPath();
}
bool RequireResolver::substituteAliasIfPresent(std::string& path)
{
if (path.size() < 1 || path[0] != '@')
return true;
// To ignore the '@' alias prefix when processing the alias
const size_t aliasStartPos = 1;
// If a directory separator was found, the length of the alias is the
// distance between the start of the alias and the separator. Otherwise,
// the whole string after the alias symbol is the alias.
size_t aliasLen = path.find_first_of("\\/");
if (aliasLen != std::string::npos)
aliasLen -= aliasStartPos;
const std::string potentialAlias = path.substr(aliasStartPos, aliasLen);
// Not worth searching when potentialAlias cannot be an alias
if (!Luau::isValidAlias(potentialAlias))
{
errorHandler.reportError("@" + potentialAlias + " is not a valid alias");
return false;
}
if (std::optional<std::string> alias = getAlias(potentialAlias))
{
path = *alias + path.substr(potentialAlias.size() + 1);
return true;
}
errorHandler.reportError("@" + potentialAlias + " is not a valid alias");
return false;
}
std::optional<std::string> RequireResolver::getAlias(std::string alias)
{
std::transform(
alias.begin(),
alias.end(),
alias.begin(),
[](unsigned char c)
{
return ('A' <= c && c <= 'Z') ? (c + ('a' - 'A')) : c;
}
);
while (!config.aliases.contains(alias) && !isConfigFullyResolved)
{
if (!parseNextConfig())
return std::nullopt; // error parsing config
}
if (!config.aliases.contains(alias) && isConfigFullyResolved)
return std::nullopt; // could not find alias
const Luau::Config::AliasInfo& aliasInfo = config.aliases[alias];
return resolvePath(aliasInfo.value, aliasInfo.configLocation);
}
bool RequireResolver::parseNextConfig()
{
if (isConfigFullyResolved)
return true; // no config files left to parse
std::optional<std::string> directory;
if (lastSearchedDir.empty())
{
std::optional<std::string> requiringFile = getRequiringContextAbsolute();
if (!requiringFile)
return false;
directory = getParentPath(*requiringFile);
}
else
directory = getParentPath(lastSearchedDir);
if (directory)
{
lastSearchedDir = *directory;
if (!parseConfigInDirectory(*directory))
return false;
}
else
isConfigFullyResolved = true;
return true;
}
bool RequireResolver::parseConfigInDirectory(const std::string& directory)
{
std::string configPath = joinPaths(directory, Luau::kConfigName);
Luau::ConfigOptions::AliasOptions aliasOpts;
aliasOpts.configLocation = configPath;
aliasOpts.overwriteAliases = false;
Luau::ConfigOptions opts;
opts.aliasOptions = std::move(aliasOpts);
if (std::optional<std::string> contents = readFile(configPath))
{
std::optional<std::string> error = Luau::parseConfig(*contents, config, opts);
if (error)
{
errorHandler.reportError("error parsing " + configPath + "(" + *error + ")");
return false;
}
}
return true;
}

119
CLI/src/RequirerUtils.cpp Normal file
View file

@ -0,0 +1,119 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/RequirerUtils.h"
#include "Luau/FileUtils.h"
#include <algorithm>
#include <string>
#include <string_view>
static std::pair<PathResult::Status, std::string> getSuffixWithAmbiguityCheck(const std::string& path)
{
bool found = false;
std::string suffix;
for (const char* potentialSuffix : {".luau", ".lua"})
{
if (isFile(path + potentialSuffix))
{
if (found)
return {PathResult::Status::AMBIGUOUS, ""};
suffix = potentialSuffix;
found = true;
}
}
if (isDirectory(path))
{
if (found)
return {PathResult::Status::AMBIGUOUS, ""};
for (const char* potentialSuffix : {"/init.luau", "/init.lua"})
{
if (isFile(path + potentialSuffix))
{
if (found)
return {PathResult::Status::AMBIGUOUS, ""};
suffix = potentialSuffix;
found = true;
}
}
found = true;
}
if (!found)
return {PathResult::Status::NOT_FOUND, ""};
return {PathResult::Status::SUCCESS, suffix};
}
static PathResult addSuffix(PathResult partialResult)
{
if (partialResult.status != PathResult::Status::SUCCESS)
return partialResult;
auto [status, suffix] = getSuffixWithAmbiguityCheck(partialResult.absPath);
if (status != PathResult::Status::SUCCESS)
return PathResult{status};
partialResult.suffix = std::move(suffix);
return partialResult;
}
PathResult getStdInResult()
{
std::optional<std::string> cwd = getCurrentWorkingDirectory();
if (!cwd)
return PathResult{PathResult::Status::NOT_FOUND};
std::replace(cwd->begin(), cwd->end(), '\\', '/');
return PathResult{PathResult::Status::SUCCESS, *cwd + "/stdin", "./stdin", ""};
}
PathResult getAbsolutePathResult(const std::string& path)
{
return addSuffix(PathResult{PathResult::Status::SUCCESS, path});
}
PathResult tryGetRelativePathResult(const std::string& path)
{
if (isAbsolutePath(path))
return getAbsolutePathResult(path);
std::optional<std::string> cwd = getCurrentWorkingDirectory();
if (!cwd)
return PathResult{PathResult::Status::NOT_FOUND};
std::optional<std::string> resolvedAbsPath = resolvePath(path, *cwd + "/stdin");
if (!resolvedAbsPath)
return PathResult{PathResult::Status::NOT_FOUND};
return addSuffix(PathResult{PathResult::Status::SUCCESS, std::move(*resolvedAbsPath), path});
}
PathResult getParent(const std::string& absPath, const std::string& relPath)
{
std::optional<std::string> parent = getParentPath(absPath);
if (!parent)
return PathResult{PathResult::Status::NOT_FOUND};
return addSuffix(PathResult{PathResult::Status::SUCCESS, *parent, normalizePath(relPath + "/..")});
}
PathResult getChild(const std::string& absPath, const std::string& relPath, const std::string& name)
{
return addSuffix(PathResult{PathResult::Status::SUCCESS, joinPaths(absPath, name), joinPaths(relPath, name)});
}
bool isFilePresent(const std::string& path, const std::string& suffix)
{
return isFile(path + suffix);
}
std::optional<std::string> getFileContents(const std::string& path, const std::string& suffix)
{
return readFile(path + suffix);
}

View file

@ -4,7 +4,7 @@ if(EXT_PLATFORM_STRING)
return() return()
endif() endif()
cmake_minimum_required(VERSION 3.0) cmake_minimum_required(VERSION 3.10)
option(LUAU_BUILD_CLI "Build CLI" ON) option(LUAU_BUILD_CLI "Build CLI" ON)
option(LUAU_BUILD_TESTS "Build tests" ON) option(LUAU_BUILD_TESTS "Build tests" ON)
@ -31,6 +31,8 @@ add_library(Luau.Analysis STATIC)
add_library(Luau.EqSat STATIC) add_library(Luau.EqSat STATIC)
add_library(Luau.CodeGen STATIC) add_library(Luau.CodeGen STATIC)
add_library(Luau.VM STATIC) add_library(Luau.VM STATIC)
add_library(Luau.Require STATIC)
add_library(Luau.RequireNavigator STATIC)
add_library(isocline STATIC) add_library(isocline STATIC)
if(LUAU_BUILD_CLI) if(LUAU_BUILD_CLI)
@ -101,6 +103,15 @@ target_compile_features(Luau.VM PRIVATE cxx_std_11)
target_include_directories(Luau.VM PUBLIC VM/include) target_include_directories(Luau.VM PUBLIC VM/include)
target_link_libraries(Luau.VM PUBLIC Luau.Common) target_link_libraries(Luau.VM PUBLIC Luau.Common)
target_compile_features(Luau.Require PUBLIC cxx_std_17)
target_include_directories(Luau.Require PUBLIC Require/Runtime/include)
target_link_libraries(Luau.Require PUBLIC Luau.VM)
target_link_libraries(Luau.Require PRIVATE Luau.RequireNavigator)
target_compile_features(Luau.RequireNavigator PUBLIC cxx_std_17)
target_include_directories(Luau.RequireNavigator PUBLIC Require/Navigator/include)
target_link_libraries(Luau.RequireNavigator PUBLIC Luau.Config)
target_include_directories(isocline PUBLIC extern/isocline/include) target_include_directories(isocline PUBLIC extern/isocline/include)
target_include_directories(Luau.VM.Internals INTERFACE VM/src) target_include_directories(Luau.VM.Internals INTERFACE VM/src)
@ -215,12 +226,12 @@ if(LUAU_BUILD_CLI)
target_include_directories(Luau.Repl.CLI PRIVATE extern extern/isocline/include) target_include_directories(Luau.Repl.CLI PRIVATE extern extern/isocline/include)
target_link_libraries(Luau.Repl.CLI PRIVATE Luau.Compiler Luau.Config Luau.CodeGen Luau.VM Luau.CLI.lib isocline) target_link_libraries(Luau.Repl.CLI PRIVATE Luau.Compiler Luau.Config Luau.CodeGen Luau.VM Luau.Require Luau.CLI.lib isocline)
target_link_libraries(Luau.Repl.CLI PRIVATE osthreads) target_link_libraries(Luau.Repl.CLI PRIVATE osthreads)
target_link_libraries(Luau.Analyze.CLI PRIVATE osthreads) target_link_libraries(Luau.Analyze.CLI PRIVATE osthreads)
target_link_libraries(Luau.Analyze.CLI PRIVATE Luau.Analysis Luau.CLI.lib) target_link_libraries(Luau.Analyze.CLI PRIVATE Luau.Analysis Luau.CLI.lib Luau.RequireNavigator)
target_link_libraries(Luau.Ast.CLI PRIVATE Luau.Ast Luau.Analysis Luau.CLI.lib) target_link_libraries(Luau.Ast.CLI PRIVATE Luau.Ast Luau.Analysis Luau.CLI.lib)
@ -252,7 +263,7 @@ if(LUAU_BUILD_TESTS)
target_compile_options(Luau.CLI.Test PRIVATE ${LUAU_OPTIONS}) target_compile_options(Luau.CLI.Test PRIVATE ${LUAU_OPTIONS})
target_include_directories(Luau.CLI.Test PRIVATE extern CLI) target_include_directories(Luau.CLI.Test PRIVATE extern CLI)
target_link_libraries(Luau.CLI.Test PRIVATE Luau.Compiler Luau.Config Luau.CodeGen Luau.VM Luau.CLI.lib isocline) target_link_libraries(Luau.CLI.Test PRIVATE Luau.Compiler Luau.Config Luau.CodeGen Luau.VM Luau.Require Luau.CLI.lib isocline)
target_link_libraries(Luau.CLI.Test PRIVATE osthreads) target_link_libraries(Luau.CLI.Test PRIVATE osthreads)
endif() endif()

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