Compare commits

...

14 commits

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

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

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

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

# New Type Solver

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

# Internal Contributors

Co-authored-by: Andy Friesen <afriesen@roblox.com>
Co-authored-by: Ariel Weiss <aaronweiss@roblox.com>
Co-authored-by: Hunter Goldstein <hgoldstein@roblox.com>
Co-authored-by: Talha Pathan <tpathan@roblox.com>
Co-authored-by: Varun Saini <vsaini@roblox.com>
Co-authored-by: Vighnesh Vijay <vvijay@roblox.com>

---------

Co-authored-by: Hunter Goldstein <hgoldstein@roblox.com>
Co-authored-by: Varun Saini <61795485+vrn-sn@users.noreply.github.com>
Co-authored-by: Alexander Youngblood <ayoungblood@roblox.com>
Co-authored-by: Menarul Alam <malam@roblox.com>
Co-authored-by: Aviral Goel <agoel@roblox.com>
Co-authored-by: Vighnesh <vvijay@roblox.com>
Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
Co-authored-by: Andy Friesen <afriesen@roblox.com>
2025-05-02 14:00:23 -07:00
Varun Saini
d9aa88e772
Fix crash when require is called from root VM stack (#1788)
Copied from #1785:
> If require is called from the root interpreter stack (e.g. using C
API) then lua_getinfo call will not succeed, leaving garbage in
lua_Debug ar struct.
> Accessing later ar.source as null-terminated string is unsafe and can
cause a crash.
> 
> This PR adds a check to ensure that lua_getinfo call is successful.

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

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

## New Solver

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

Co-authored-by: Andy Friesen <afriesen@roblox.com>
Co-authored-by: Ariel Weiss <aaronweiss@roblox.com>
Co-authored-by: Hunter Goldstein <hgoldstein@roblox.com>
Co-authored-by: Sora Kanosue <skanosue@roblox.com>
Co-authored-by: Talha Pathan <tpathan@roblox.com>
Co-authored-by: Varun Saini <vsaini@roblox.com>
Co-authored-by: Vighnesh Vijay <vvijay@roblox.com>
Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
2025-04-25 14:19:27 -07:00
Vighnesh-V
a2303a6ae6
Sync to upstream/release/670 (#1779)
# General 
This week has been focused primarily on bugfixes, with a ton of
usability improvements to the new solver, fragment autocomplete, and the
concrete syntax tree project.

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

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

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

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

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

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

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

View file

@ -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

@ -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

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

View file

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

View file

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

View file

@ -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);
@ -179,7 +173,7 @@ private:
ControlFlow visit(AstStatTypeFunction* f); ControlFlow visit(AstStatTypeFunction* f);
ControlFlow visit(AstStatDeclareGlobal* d); ControlFlow visit(AstStatDeclareGlobal* d);
ControlFlow visit(AstStatDeclareFunction* d); ControlFlow visit(AstStatDeclareFunction* d);
ControlFlow visit(AstStatDeclareClass* d); ControlFlow visit(AstStatDeclareExternType* d);
ControlFlow visit(AstStatError* error); ControlFlow visit(AstStatError* error);
DataFlowResult visitExpr(AstExpr* e); DataFlowResult visitExpr(AstExpr* e);

View file

@ -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

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

View file

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

View file

@ -49,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
@ -59,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
@ -72,14 +75,33 @@ 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(
AstStatBlock* root, AstStatBlock* stale,
AstStatBlock* mostRecentParse,
AstNameTable* names, AstNameTable* names,
std::string_view src, std::string_view src,
const Position& cursorPos, const Position& cursorPos,
@ -93,6 +115,7 @@ std::pair<FragmentTypeCheckStatus, FragmentTypeCheckResult> typecheckFragment(
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 IFragmentAutocompleteReporter* reporter = nullptr
); );
@ -104,6 +127,7 @@ FragmentAutocompleteResult fragmentAutocomplete(
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 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>
@ -34,7 +33,6 @@ struct HotComment;
struct BuildQueueItem; struct BuildQueueItem;
struct BuildQueueWorkState; struct BuildQueueWorkState;
struct FrontendCancellationToken; struct FrontendCancellationToken;
struct AnyTypeSummary;
struct LoadDefinitionFileResult struct LoadDefinitionFileResult
{ {
@ -217,11 +215,6 @@ struct Frontend
std::function<void(std::function<void()> task)> executeTask = {}, std::function<void(std::function<void()> task)> executeTask = {},
std::function<bool(size_t done, size_t total)> progress = {} std::function<bool(size_t done, size_t total)> progress = {}
); );
std::vector<ModuleName> checkQueuedModules_DEPRECATED(
std::optional<FrontendOptions> optionOverride = {},
std::function<void(std::function<void()> task)> executeTask = {},
std::function<bool(size_t done, size_t total)> progress = {}
);
std::optional<CheckResult> getCheckResult(const ModuleName& name, bool accumulateNested, bool forAutocomplete = false); std::optional<CheckResult> getCheckResult(const ModuleName& name, bool accumulateNested, bool forAutocomplete = false);
std::vector<ModuleName> getRequiredScripts(const ModuleName& name); std::vector<ModuleName> getRequiredScripts(const ModuleName& name);
@ -305,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
@ -319,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,75 @@
namespace Luau namespace Luau
{ {
template<typename TID>
struct GeneralizationParams
{
bool foundOutsideFunctions = false;
size_t useCount = 0;
Polarity polarity = Polarity::None;
};
template<typename TID>
struct GeneralizationResult
{
std::optional<TID> result;
// True if the provided type was replaced with a generic.
bool wasReplacedByGeneric = false;
bool resourceLimitsExceeded = false;
explicit operator bool() const
{
return bool(result);
}
};
// Replace a single free type by its bounds according to the polarity provided.
GeneralizationResult<TypeId> generalizeType(
NotNull<TypeArena> arena,
NotNull<BuiltinTypes> builtinTypes,
NotNull<Scope> scope,
TypeId freeTy,
const GeneralizationParams<TypeId>& params
);
// Generalize one type pack
GeneralizationResult<TypePackId> generalizeTypePack(
NotNull<TypeArena> arena,
NotNull<BuiltinTypes> builtinTypes,
NotNull<Scope> scope,
TypePackId tp,
const GeneralizationParams<TypePackId>& params
);
void sealTable(NotNull<Scope> scope, TypeId ty);
/** Attempt to generalize a type.
*
* If generalizationTarget is set, then only that type will be replaced by its
* bounds. The way this is intended to be used is that ty is some function that
* is not fully generalized, and generalizationTarget is a type within its
* signature. There should be no further constraints that could affect the
* bounds of generalizationTarget.
*
* Returns nullopt if generalization failed due to resources limits.
*/
std::optional<TypeId> generalize( std::optional<TypeId> generalize(
NotNull<TypeArena> arena, NotNull<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 std::optional<TypeId> generalizationTarget = {}
); );
}
void pruneUnnecessaryGenerics(
NotNull<TypeArena> arena,
NotNull<BuiltinTypes> builtinTypes,
NotNull<Scope> scope,
NotNull<DenseHashSet<TypeId>> cachedTypes,
TypeId ty
);
} // namespace Luau

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

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

View file

@ -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,10 +82,6 @@ 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;

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

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

View file

@ -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

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

View file

@ -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

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

View file

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

View file

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

View file

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

View file

@ -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,12 +72,8 @@ 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
explicit FreeType(TypeLevel level);
explicit FreeType(Scope* scope);
FreeType(Scope* scope, TypeLevel level);
int index; int index;
TypeLevel level; TypeLevel level;
@ -91,6 +87,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 +97,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 +108,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,
@ -287,7 +287,7 @@ struct MagicFunctionCallContext
{ {
NotNull<struct ConstraintSolver> solver; NotNull<struct ConstraintSolver> solver;
NotNull<const Constraint> constraint; NotNull<const Constraint> constraint;
const class AstExprCall* callSite; NotNull<const AstExprCall> callSite;
TypePackId arguments; TypePackId arguments;
TypePackId result; TypePackId result;
}; };
@ -348,10 +348,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 +366,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 +374,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 +383,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 +460,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 +507,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.
@ -545,15 +532,15 @@ struct ClassUserData
virtual ~ClassUserData() {} virtual ~ClassUserData() {}
}; };
/** The type of a class. /** The type of an external userdata exposed to Luau.
* *
* Classes behave like tables in many ways, but there are some important differences: * Extern types behave like tables in many ways, but there are some important differences:
* *
* The properties of a class are always exactly known. * The properties of a class are always exactly known.
* Classes optionally have a parent class. * Extern types optionally have a parent type.
* Two different classes that share the same properties are nevertheless distinct and mutually incompatible. * Two different extern types that share the same properties are nevertheless distinct and mutually incompatible.
*/ */
struct ClassType struct ExternType
{ {
using Props = TableType::Props; using Props = TableType::Props;
@ -567,7 +554,7 @@ struct ClassType
std::optional<Location> definitionLocation; std::optional<Location> definitionLocation;
std::optional<TableIndexer> indexer; std::optional<TableIndexer> indexer;
ClassType( ExternType(
Name name, Name name,
Props props, Props props,
std::optional<TypeId> parent, std::optional<TypeId> parent,
@ -588,7 +575,7 @@ struct ClassType
{ {
} }
ClassType( ExternType(
Name name, Name name,
Props props, Props props,
std::optional<TypeId> parent, std::optional<TypeId> parent,
@ -622,7 +609,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{""};
}; };
/** /**
@ -789,7 +775,7 @@ using TypeVariant = Unifiable::Variant<
FunctionType, FunctionType,
TableType, TableType,
MetatableType, MetatableType,
ClassType, ExternType,
AnyType, AnyType,
UnionType, UnionType,
IntersectionType, IntersectionType,
@ -882,6 +868,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 +878,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)
{ {
} }
@ -990,7 +986,7 @@ public:
const TypeId threadType; const TypeId threadType;
const TypeId bufferType; const TypeId bufferType;
const TypeId functionType; const TypeId functionType;
const TypeId classType; const TypeId externType;
const TypeId tableType; const TypeId tableType;
const TypeId emptyTableType; const TypeId emptyTableType;
const TypeId trueType; const TypeId trueType;
@ -1002,6 +998,7 @@ public:
const TypeId noRefineType; const TypeId noRefineType;
const TypeId falsyType; const TypeId falsyType;
const TypeId truthyType; const TypeId truthyType;
const TypeId notNilType;
const TypeId optionalNumberType; const TypeId optionalNumberType;
const TypeId optionalStringType; const TypeId optionalStringType;
@ -1022,10 +1019,10 @@ TypeLevel* getMutableLevel(TypeId ty);
std::optional<TypeLevel> getLevel(TypePackId tp); std::optional<TypeLevel> getLevel(TypePackId tp);
const Property* lookupClassProp(const ClassType* cls, const Name& name); const Property* lookupExternTypeProp(const ExternType* cls, const Name& name);
// Whether `cls` is a subclass of `parent` // Whether `cls` is a subclass of `parent`
bool isSubclass(const ClassType* cls, const ClassType* parent); bool isSubclass(const ExternType* cls, const ExternType* parent);
Type* asMutable(TypeId ty); Type* asMutable(TypeId ty);
@ -1202,7 +1199,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"
@ -36,11 +37,7 @@ struct TypeArena
TypeId freshType(NotNull<BuiltinTypes> builtins, Scope* scope); TypeId freshType(NotNull<BuiltinTypes> builtins, Scope* scope);
TypeId freshType(NotNull<BuiltinTypes> builtins, Scope* scope, TypeLevel level); TypeId freshType(NotNull<BuiltinTypes> builtins, Scope* scope, TypeLevel level);
TypeId freshType_DEPRECATED(TypeLevel level); TypePackId freshTypePack(Scope* scope, Polarity polarity = Polarity::Unknown);
TypeId freshType_DEPRECATED(Scope* scope);
TypeId freshType_DEPRECATED(Scope* scope, TypeLevel level);
TypePackId freshTypePack(Scope* scope);
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

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

View file

@ -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 (FFlag::LuauImproveTypePathsInErrors)
{
if (reasons.size() > 1)
allReasons += "\n\t * ";
}
else
{ {
if (first) if (first)
first = false; first = false;
else else
allReasons += "\n\t"; allReasons += "\n\t";
}
allReasons += reason; allReasons += reason;
} }
@ -147,7 +160,7 @@ private:
void visit(AstTypeList types); void visit(AstTypeList types);
void visit(AstStatDeclareFunction* stat); void visit(AstStatDeclareFunction* stat);
void visit(AstStatDeclareGlobal* stat); void visit(AstStatDeclareGlobal* stat);
void visit(AstStatDeclareClass* stat); void visit(AstStatDeclareExternType* stat);
void visit(AstStatError* stat); void visit(AstStatError* stat);
void visit(AstExpr* expr, ValueContext context); void visit(AstExpr* expr, ValueContext context);
void visit(AstExprGroup* expr, ValueContext context); void visit(AstExprGroup* expr, ValueContext context);

View file

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

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

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

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

View file

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

View file

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

View file

@ -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

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

View file

@ -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;
@ -113,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

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

View file

@ -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

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

View file

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

View file

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

View file

@ -13,8 +13,6 @@
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauExtendStatEndPosWithSemicolon)
namespace Luau namespace Luau
{ {
@ -42,8 +40,6 @@ 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 // 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 // to `AstStatLocal.location.end`. However, in the first case (semicolon), we are starting a new statement, whilst in the second case
@ -53,15 +49,6 @@ struct AutocompleteNodeFinder : public AstVisitor
ancestry.push_back(stat); ancestry.push_back(stat);
return true; return true;
} }
}
else
{
if (stat->location.begin < pos && pos <= stat->location.end)
{
ancestry.push_back(stat);
return true;
}
}
return false; return false;
} }
@ -587,11 +574,11 @@ std::optional<DocumentationSymbol> getDocumentationSymbolAtPosition(const Source
return checkOverloadedDocumentationSymbol(module, propIt->second.type(), parentExpr, propIt->second.documentationSymbol); return checkOverloadedDocumentationSymbol(module, propIt->second.type(), parentExpr, propIt->second.documentationSymbol);
} }
} }
else if (const ClassType* ctv = get<ClassType>(parentTy)) else if (const ExternType* etv = get<ExternType>(parentTy))
{ {
while (ctv) while (etv)
{ {
if (auto propIt = ctv->props.find(indexName->index.value); propIt != ctv->props.end()) if (auto propIt = etv->props.find(indexName->index.value); propIt != etv->props.end())
{ {
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
{ {
@ -603,7 +590,7 @@ std::optional<DocumentationSymbol> getDocumentationSymbolAtPosition(const Source
module, propIt->second.type(), parentExpr, propIt->second.documentationSymbol module, propIt->second.type(), parentExpr, propIt->second.documentationSymbol
); );
} }
ctv = ctv->parent ? Luau::get<Luau::ClassType>(*ctv->parent) : nullptr; etv = etv->parent ? Luau::get<Luau::ExternType>(*etv->parent) : nullptr;
} }
} }
else if (const PrimitiveType* ptv = get<PrimitiveType>(parentTy); ptv && ptv->metatable) else if (const PrimitiveType* ptv = get<PrimitiveType>(parentTy); ptv && ptv->metatable)

View file

@ -24,12 +24,10 @@ LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTINT(LuauTypeInferIterationLimit) LUAU_FASTINT(LuauTypeInferIterationLimit)
LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAGVARIABLE(DebugLuauMagicVariableNames) LUAU_FASTFLAGVARIABLE(DebugLuauMagicVariableNames)
LUAU_FASTFLAG(LuauExposeRequireByStringAutocomplete)
LUAU_FASTFLAGVARIABLE(LuauAutocompleteRefactorsForIncrementalAutocomplete)
LUAU_FASTFLAGVARIABLE(LuauAutocompleteUsesModuleForTypeCompatibility) LUAU_FASTFLAGVARIABLE(LuauAutocompleteUsesModuleForTypeCompatibility)
LUAU_FASTFLAGVARIABLE(LuauAutocompleteUnionCopyPreviousSeen)
LUAU_FASTFLAGVARIABLE(LuauAutocompleteMissingFollows)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
static const std::unordered_set<std::string> kStatementStartingKeywords = static const std::unordered_set<std::string> kStatementStartingKeywords =
{"while", "if", "local", "repeat", "function", "do", "for", "return", "break", "continue", "type", "export"}; {"while", "if", "local", "repeat", "function", "do", "for", "return", "break", "continue", "type", "export"};
@ -82,6 +80,8 @@ static ParenthesesRecommendation getParenRecommendationForIntersect(const Inters
ParenthesesRecommendation rec = ParenthesesRecommendation::None; ParenthesesRecommendation rec = ParenthesesRecommendation::None;
for (Luau::TypeId partId : intersect->parts) for (Luau::TypeId partId : intersect->parts)
{ {
if (FFlag::LuauAutocompleteMissingFollows)
partId = follow(partId);
if (auto partFunc = Luau::get<FunctionType>(partId)) if (auto partFunc = Luau::get<FunctionType>(partId))
{ {
rec = std::max(rec, getParenRecommendationForFunc(partFunc, nodes)); rec = std::max(rec, getParenRecommendationForFunc(partFunc, nodes));
@ -307,7 +307,7 @@ static void autocompleteProps(
const std::vector<AstNode*>& nodes, const std::vector<AstNode*>& nodes,
AutocompleteEntryMap& result, AutocompleteEntryMap& result,
std::unordered_set<TypeId>& seen, std::unordered_set<TypeId>& seen,
std::optional<const ClassType*> containingClass = std::nullopt std::optional<const ExternType*> containingExternType = std::nullopt
) )
{ {
rootTy = follow(rootTy); rootTy = follow(rootTy);
@ -330,8 +330,8 @@ static void autocompleteProps(
if (calledWithSelf == ftv->hasSelf) if (calledWithSelf == ftv->hasSelf)
return true; return true;
// Calls on classes require strict match between how function is declared and how it's called // Calls on extern types require strict match between how function is declared and how it's called
if (get<ClassType>(rootTy)) if (get<ExternType>(rootTy))
return false; return false;
// When called with ':', but declared without 'self', it is invalid if a function has incompatible first argument or no arguments at all // When called with ':', but declared without 'self', it is invalid if a function has incompatible first argument or no arguments at all
@ -364,7 +364,7 @@ static void autocompleteProps(
return calledWithSelf; return calledWithSelf;
}; };
auto fillProps = [&](const ClassType::Props& props) auto fillProps = [&](const ExternType::Props& props)
{ {
for (const auto& [name, prop] : props) for (const auto& [name, prop] : props)
{ {
@ -397,7 +397,7 @@ static void autocompleteProps(
prop.deprecated, prop.deprecated,
isWrongIndexer(type), isWrongIndexer(type),
typeCorrect, typeCorrect,
containingClass, containingExternType,
&prop, &prop,
prop.documentationSymbol, prop.documentationSymbol,
{}, {},
@ -428,12 +428,12 @@ static void autocompleteProps(
} }
}; };
if (auto cls = get<ClassType>(ty)) if (auto cls = get<ExternType>(ty))
{ {
containingClass = containingClass.value_or(cls); containingExternType = containingExternType.value_or(cls);
fillProps(cls->props); fillProps(cls->props);
if (cls->parent) if (cls->parent)
autocompleteProps(module, typeArena, builtinTypes, rootTy, *cls->parent, indexType, nodes, result, seen, containingClass); autocompleteProps(module, typeArena, builtinTypes, rootTy, *cls->parent, indexType, nodes, result, seen, containingExternType);
} }
else if (auto tbl = get<TableType>(ty)) else if (auto tbl = get<TableType>(ty))
fillProps(tbl->props); fillProps(tbl->props);
@ -484,6 +484,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 | ExternType
//
// 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;
@ -572,7 +587,7 @@ AutocompleteEntryMap autocompleteProps(
AutocompleteEntryMap autocompleteModuleTypes(const Module& module, const ScopePtr& scopeAtPosition, Position position, std::string_view moduleName) AutocompleteEntryMap autocompleteModuleTypes(const Module& module, const ScopePtr& scopeAtPosition, Position position, std::string_view moduleName)
{ {
AutocompleteEntryMap result; AutocompleteEntryMap result;
ScopePtr startScope = FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete ? scopeAtPosition : findScopeAtPosition(module, position); ScopePtr startScope = scopeAtPosition;
for (ScopePtr& scope = startScope; scope; scope = scope->parent) for (ScopePtr& scope = startScope; scope; scope = scope->parent)
{ {
if (auto it = scope->importedTypeBindings.find(std::string(moduleName)); it != scope->importedTypeBindings.end()) if (auto it = scope->importedTypeBindings.find(std::string(moduleName)); it != scope->importedTypeBindings.end())
@ -684,6 +699,30 @@ static std::optional<TypeId> findTypeElementAt(const AstTypeList& astTypeList, T
return {}; return {};
} }
static std::optional<TypeId> findTypeElementAt(AstTypePack* astTypePack, TypePackId tp, Position position)
{
LUAU_ASSERT(FFlag::LuauStoreReturnTypesAsPackOnAst);
if (const auto typePack = astTypePack->as<AstTypePackExplicit>())
{
return findTypeElementAt(typePack->typeList, tp, position);
}
else if (const auto variadic = astTypePack->as<AstTypePackVariadic>())
{
if (variadic->location.containsClosed(position))
{
auto [_, tail] = flatten(tp);
if (tail)
{
if (const VariadicTypePack* vtp = get<VariadicTypePack>(follow(*tail)))
return findTypeElementAt(variadic->variadicType, vtp->ty, position);
}
}
}
return {};
}
static std::optional<TypeId> findTypeElementAt(AstType* astType, TypeId ty, Position position) static std::optional<TypeId> findTypeElementAt(AstType* astType, TypeId ty, Position position)
{ {
ty = follow(ty); ty = follow(ty);
@ -704,9 +743,17 @@ static std::optional<TypeId> findTypeElementAt(AstType* astType, TypeId ty, Posi
if (auto element = findTypeElementAt(type->argTypes, ftv->argTypes, position)) if (auto element = findTypeElementAt(type->argTypes, ftv->argTypes, position))
return element; return element;
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
if (auto element = findTypeElementAt(type->returnTypes, ftv->retTypes, position)) if (auto element = findTypeElementAt(type->returnTypes, ftv->retTypes, position))
return element; return element;
} }
else
{
if (auto element = findTypeElementAt(type->returnTypes_DEPRECATED, ftv->retTypes, position))
return element;
}
}
// It's possible to walk through other types like intrsection and unions if we find value in doing that // It's possible to walk through other types like intrsection and unions if we find value in doing that
return {}; return {};
@ -714,7 +761,7 @@ static std::optional<TypeId> findTypeElementAt(AstType* astType, TypeId ty, Posi
std::optional<TypeId> getLocalTypeInScopeAt(const Module& module, const ScopePtr& scopeAtPosition, Position position, AstLocal* local) std::optional<TypeId> getLocalTypeInScopeAt(const Module& module, const ScopePtr& scopeAtPosition, Position position, AstLocal* local)
{ {
if (ScopePtr scope = FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete ? scopeAtPosition : findScopeAtPosition(module, position)) if (ScopePtr scope = scopeAtPosition)
{ {
for (const auto& [name, binding] : scope->bindings) for (const auto& [name, binding] : scope->bindings)
{ {
@ -856,7 +903,7 @@ AutocompleteEntryMap autocompleteTypeNames(
{ {
AutocompleteEntryMap result; AutocompleteEntryMap result;
ScopePtr startScope = FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete ? scopeAtPosition : findScopeAtPosition(module, position); ScopePtr startScope = scopeAtPosition;
for (ScopePtr scope = startScope; scope; scope = scope->parent) for (ScopePtr scope = startScope; scope; scope = scope->parent)
{ {
@ -1035,12 +1082,16 @@ AutocompleteEntryMap autocompleteTypeNames(
} }
} }
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
if (!node->returnAnnotation) if (!node->returnAnnotation)
return result; return result;
for (size_t i = 0; i < node->returnAnnotation->types.size; i++) if (const auto typePack = node->returnAnnotation->as<AstTypePackExplicit>())
{ {
AstType* ret = node->returnAnnotation->types.data[i]; for (size_t i = 0; i < typePack->typeList.types.size; i++)
{
AstType* ret = typePack->typeList.types.data[i];
if (ret->location.containsClosed(position)) if (ret->location.containsClosed(position))
{ {
@ -1055,7 +1106,7 @@ AutocompleteEntryMap autocompleteTypeNames(
} }
} }
if (AstTypePack* retTp = node->returnAnnotation->tailType) if (AstTypePack* retTp = typePack->typeList.tailType)
{ {
if (auto variadic = retTp->as<AstTypePackVariadic>()) if (auto variadic = retTp->as<AstTypePackVariadic>())
{ {
@ -1070,6 +1121,56 @@ AutocompleteEntryMap autocompleteTypeNames(
} }
} }
} }
else if (auto variadic = node->returnAnnotation->as<AstTypePackVariadic>())
{
if (variadic->location.containsClosed(position))
{
if (const FunctionType* ftv = tryGetExpectedFunctionType(module, node))
{
if (auto ty = tryGetTypePackTypeAt(ftv->retTypes, ~0u))
tryAddTypeCorrectSuggestion(result, startScope, topType, *ty, position);
}
}
}
}
else
{
if (!node->returnAnnotation_DEPRECATED)
return result;
for (size_t i = 0; i < node->returnAnnotation_DEPRECATED->types.size; i++)
{
AstType* ret = node->returnAnnotation_DEPRECATED->types.data[i];
if (ret->location.containsClosed(position))
{
if (const FunctionType* ftv = tryGetExpectedFunctionType(module, node))
{
if (auto ty = tryGetTypePackTypeAt(ftv->retTypes, i))
tryAddTypeCorrectSuggestion(result, startScope, topType, *ty, position);
}
// TODO: with additional type information, we could suggest inferred return type here
break;
}
}
if (AstTypePack* retTp = node->returnAnnotation_DEPRECATED->tailType)
{
if (auto variadic = retTp->as<AstTypePackVariadic>())
{
if (variadic->location.containsClosed(position))
{
if (const FunctionType* ftv = tryGetExpectedFunctionType(module, node))
{
if (auto ty = tryGetTypePackTypeAt(ftv->retTypes, ~0u))
tryAddTypeCorrectSuggestion(result, startScope, topType, *ty, position);
}
}
}
}
}
}
return result; return result;
} }
@ -1189,7 +1290,7 @@ static AutocompleteEntryMap autocompleteStatement(
) )
{ {
// This is inefficient. :( // This is inefficient. :(
ScopePtr scope = FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete ? scopeAtPosition : findScopeAtPosition(module, position); ScopePtr scope = scopeAtPosition;
AutocompleteEntryMap result; AutocompleteEntryMap result;
@ -1367,7 +1468,7 @@ static AutocompleteContext autocompleteExpression(
else else
{ {
// This is inefficient. :( // This is inefficient. :(
ScopePtr scope = FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete ? scopeAtPosition : findScopeAtPosition(module, position); ScopePtr scope = scopeAtPosition;
while (scope) while (scope)
{ {
@ -1436,7 +1537,7 @@ static AutocompleteResult autocompleteExpression(
return {result, ancestry, context}; return {result, ancestry, context};
} }
static std::optional<const ClassType*> getMethodContainingClass(const ModulePtr& module, AstExpr* funcExpr) static std::optional<const ExternType*> getMethodContainingExternType(const ModulePtr& module, AstExpr* funcExpr)
{ {
AstExpr* parentExpr = nullptr; AstExpr* parentExpr = nullptr;
if (auto indexName = funcExpr->as<AstExprIndexName>()) if (auto indexName = funcExpr->as<AstExprIndexName>())
@ -1460,14 +1561,14 @@ static std::optional<const ClassType*> getMethodContainingClass(const ModulePtr&
Luau::TypeId parentType = Luau::follow(*parentIt); Luau::TypeId parentType = Luau::follow(*parentIt);
if (auto parentClass = Luau::get<ClassType>(parentType)) if (auto parentExternType = Luau::get<ExternType>(parentType))
{ {
return parentClass; return parentExternType;
} }
if (auto parentUnion = Luau::get<UnionType>(parentType)) if (auto parentUnion = Luau::get<UnionType>(parentType))
{ {
return returnFirstNonnullOptionOfType<ClassType>(parentUnion); return returnFirstNonnullOptionOfType<ExternType>(parentUnion);
} }
return std::nullopt; return std::nullopt;
@ -1525,10 +1626,7 @@ static std::optional<AutocompleteEntryMap> convertRequireSuggestionsToAutocomple
{ {
AutocompleteEntry entry = {AutocompleteEntryKind::RequirePath}; AutocompleteEntry entry = {AutocompleteEntryKind::RequirePath};
entry.insertText = std::move(suggestion.fullPath); entry.insertText = std::move(suggestion.fullPath);
if (FFlag::LuauExposeRequireByStringAutocomplete)
{
entry.tags = std::move(suggestion.tags); entry.tags = std::move(suggestion.tags);
}
result[std::move(suggestion.label)] = std::move(entry); result[std::move(suggestion.label)] = std::move(entry);
} }
return result; return result;
@ -1589,7 +1687,7 @@ static std::optional<AutocompleteEntryMap> autocompleteStringParams(
{ {
return convertRequireSuggestionsToAutocompleteEntryMap(fileResolver->getRequireSuggestions(module->name, candidateString)); return convertRequireSuggestionsToAutocompleteEntryMap(fileResolver->getRequireSuggestions(module->name, candidateString));
} }
if (std::optional<AutocompleteEntryMap> ret = callback(tag, getMethodContainingClass(module, candidate->func), candidateString)) if (std::optional<AutocompleteEntryMap> ret = callback(tag, getMethodContainingExternType(module, candidate->func), candidateString))
{ {
return ret; return ret;
} }
@ -1607,6 +1705,8 @@ static std::optional<AutocompleteEntryMap> autocompleteStringParams(
{ {
for (TypeId part : intersect->parts) for (TypeId part : intersect->parts)
{ {
if (FFlag::LuauAutocompleteMissingFollows)
part = follow(part);
if (auto candidateFunctionType = Luau::get<FunctionType>(part)) if (auto candidateFunctionType = Luau::get<FunctionType>(part))
{ {
if (std::optional<AutocompleteEntryMap> ret = performCallback(candidateFunctionType)) if (std::optional<AutocompleteEntryMap> ret = performCallback(candidateFunctionType))
@ -1755,7 +1855,7 @@ static std::optional<AutocompleteEntry> makeAnonymousAutofilled(
if (!type) if (!type)
return std::nullopt; return std::nullopt;
const ScopePtr scope = FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete ? scopeAtPosition : findScopeAtPosition(*module, position); const ScopePtr scope = scopeAtPosition;
if (!scope) if (!scope)
return std::nullopt; return std::nullopt;

View file

@ -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,11 @@
*/ */
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAGVARIABLE(LuauStringFormatErrorSuppression) LUAU_FASTFLAG(LuauNonReentrantGeneralization2)
LUAU_FASTFLAGVARIABLE(LuauTableCloneClonesType3) LUAU_FASTFLAGVARIABLE(LuauTableCloneClonesType3)
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope) LUAU_FASTFLAGVARIABLE(LuauUserTypeFunTypecheck)
LUAU_FASTFLAGVARIABLE(LuauFreezeIgnorePersistent) LUAU_FASTFLAGVARIABLE(LuauMagicFreezeCheckBlocked)
LUAU_FASTFLAGVARIABLE(LuauFollowTableFreeze) LUAU_FASTFLAGVARIABLE(LuauFormatUseLastPosition)
namespace Luau namespace Luau
{ {
@ -247,6 +248,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 +290,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 +313,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::LuauNonReentrantGeneralization2
if (FFlag::LuauNonReentrantGeneralization2)
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 +325,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);
@ -322,7 +343,7 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
if (auto it = globals.globalScope->exportedTypeBindings.find("vector"); it != globals.globalScope->exportedTypeBindings.end()) if (auto it = globals.globalScope->exportedTypeBindings.find("vector"); it != globals.globalScope->exportedTypeBindings.end())
{ {
TypeId vectorTy = it->second.type; TypeId vectorTy = it->second.type;
ClassType* vectorCls = getMutable<ClassType>(vectorTy); ExternType* vectorCls = getMutable<ExternType>(vectorTy);
vectorCls->metatable = arena.addType(TableType{{}, std::nullopt, TypeLevel{}, TableState::Sealed}); vectorCls->metatable = arena.addType(TableType{{}, std::nullopt, TypeLevel{}, TableState::Sealed});
TableType* metatableTy = Luau::getMutable<TableType>(vectorCls->metatable); TableType* metatableTy = Luau::getMutable<TableType>(vectorCls->metatable);
@ -353,7 +374,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 +386,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,6 +420,12 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
// clang-format on // clang-format on
} }
if (FFlag::LuauUserTypeFunTypecheck)
{
finalizeGlobalBindings(globals.globalScope);
}
else
{
for (const auto& pair : globals.globalScope->bindings) for (const auto& pair : globals.globalScope->bindings)
{ {
persist(pair.second.typeId); persist(pair.second.typeId);
@ -409,13 +436,14 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
ttv->name = "typeof(" + toString(pair.first) + ")"; ttv->name = "typeof(" + toString(pair.first) + ")";
} }
} }
}
attachMagicFunction(getGlobalBinding(globals, "assert"), std::make_shared<MagicAssert>()); attachMagicFunction(getGlobalBinding(globals, "assert"), std::make_shared<MagicAssert>());
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 +466,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 +499,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)
@ -619,6 +704,14 @@ bool MagicFormat::typeCheck(const MagicFunctionTypeCheckContext& context)
return true; return true;
} }
// CLI-150726: The block below effectively constructs a type pack and then type checks it by going parameter-by-parameter.
// This does _not_ handle cases like:
//
// local foo : () -> (...string) = (nil :: any)
// print(string.format("%s %d %s", foo()))
//
// ... which should be disallowed.
std::vector<TypeId> expected = parseFormatString(context.builtinTypes, fmt->value.data, fmt->value.size); std::vector<TypeId> expected = parseFormatString(context.builtinTypes, fmt->value.data, fmt->value.size);
const auto& [params, tail] = flatten(context.arguments); const auto& [params, tail] = flatten(context.arguments);
@ -630,13 +723,13 @@ bool MagicFormat::typeCheck(const MagicFunctionTypeCheckContext& context)
{ {
TypeId actualTy = params[i + paramOffset]; TypeId actualTy = params[i + paramOffset];
TypeId expectedTy = expected[i]; TypeId expectedTy = expected[i];
Location location = context.callSite->args.data[i + (calledWithSelf ? 0 : paramOffset)]->location; Location location = FFlag::LuauFormatUseLastPosition
? context.callSite->args.data[std::min(context.callSite->args.size - 1, i + (calledWithSelf ? 0 : paramOffset))]->location
: context.callSite->args.data[i + (calledWithSelf ? 0 : paramOffset)]->location;
// use subtyping instead here // use subtyping instead here
SubtypingResult result = context.typechecker->subtyping->isSubtype(actualTy, expectedTy, context.checkScope); SubtypingResult result = context.typechecker->subtyping->isSubtype(actualTy, expectedTy, context.checkScope);
if (!result.isSubtype) if (!result.isSubtype)
{
if (FFlag::LuauStringFormatErrorSuppression)
{ {
switch (shouldSuppressErrors(NotNull{&context.typechecker->normalizer}, actualTy)) switch (shouldSuppressErrors(NotNull{&context.typechecker->normalizer}, actualTy))
{ {
@ -651,12 +744,6 @@ bool MagicFormat::typeCheck(const MagicFunctionTypeCheckContext& context)
context.typechecker->reportError(TypeMismatch{expectedTy, actualTy, reasonings.toString()}, location); context.typechecker->reportError(TypeMismatch{expectedTy, actualTy, reasonings.toString()}, location);
} }
} }
else
{
Reasonings reasonings = context.typechecker->explainReasonings(actualTy, expectedTy, location, result);
context.typechecker->reportError(TypeMismatch{expectedTy, actualTy, reasonings.toString()}, location);
}
}
} }
return true; return true;
@ -1444,14 +1531,13 @@ 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))
{ {
tableType->scope = context.constraint->scope.get(); tableType->scope = context.constraint->scope.get();
} }
if (FFlag::LuauTrackInteriorFreeTypesOnScope)
trackInteriorFreeType(context.constraint->scope.get(), resultType); trackInteriorFreeType(context.constraint->scope.get(), resultType);
TypePackId clonedTypePack = arena->addTypePack({resultType}); TypePackId clonedTypePack = arena->addTypePack({resultType});
@ -1463,7 +1549,6 @@ bool MagicClone::infer(const MagicFunctionCallContext& context)
static std::optional<TypeId> freezeTable(TypeId inputType, const MagicFunctionCallContext& context) static std::optional<TypeId> freezeTable(TypeId inputType, const MagicFunctionCallContext& context)
{ {
TypeArena* arena = context.solver->arena; TypeArena* arena = context.solver->arena;
if (FFlag::LuauFollowTableFreeze)
inputType = follow(inputType); inputType = follow(inputType);
if (auto mt = get<MetatableType>(inputType)) if (auto mt = get<MetatableType>(inputType))
{ {
@ -1481,7 +1566,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 +1616,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

@ -9,13 +9,12 @@
#include "Luau/VisitType.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(LuauClonedTableAndFunctionTypesMustHaveScopes)
LUAU_FASTFLAGVARIABLE(LuauDoNotClonePersistentBindings) LUAU_FASTFLAGVARIABLE(LuauDoNotClonePersistentBindings)
LUAU_FASTFLAG(LuauIncrementalAutocompleteDemandBasedCloning)
namespace Luau namespace Luau
{ {
@ -134,7 +133,7 @@ protected:
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;
} }
@ -144,7 +143,7 @@ protected:
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;
} }
@ -170,7 +169,7 @@ public:
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);
@ -180,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;
@ -196,7 +193,7 @@ public:
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);
@ -358,7 +355,7 @@ private:
t->metatable = shallowClone(t->metatable); t->metatable = shallowClone(t->metatable);
} }
void cloneChildren(ClassType* t) void cloneChildren(ExternType* t)
{ {
for (auto& [_, p] : t->props) for (auto& [_, p] : t->props)
p = shallowClone(p); p = shallowClone(p);
@ -398,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));
@ -505,7 +502,7 @@ public:
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);
@ -522,11 +519,6 @@ public:
if (FFlag::LuauClonedTableAndFunctionTypesMustHaveScopes) if (FFlag::LuauClonedTableAndFunctionTypesMustHaveScopes)
tt->scope = replacementForNullScope; tt->scope = replacementForNullScope;
} }
else if (auto fn = getMutable<FunctionType>(target))
{
if (FFlag::LuauClonedTableAndFunctionTypesMustHaveScopes)
fn->scope = replacementForNullScope;
}
(*types)[ty] = target; (*types)[ty] = target;
queue.emplace_back(target); queue.emplace_back(target);
@ -539,7 +531,7 @@ public:
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);
@ -553,6 +545,16 @@ public:
queue.emplace_back(target); queue.emplace_back(target);
return 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));
}
}
}; };
@ -560,7 +562,7 @@ public:
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{
@ -569,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);
@ -577,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{
@ -585,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
}; };

View file

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

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -13,32 +13,25 @@
LUAU_FASTFLAG(DebugLuauFreezeArena) LUAU_FASTFLAG(DebugLuauFreezeArena)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAGVARIABLE(LuauPreprocessTypestatedArgument)
LUAU_FASTFLAGVARIABLE(LuauDfgScopeStackTrueReset)
LUAU_FASTFLAGVARIABLE(LuauDfgScopeStackNotNull)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
LUAU_FASTFLAGVARIABLE(LuauDoNotAddUpvalueTypesToLocalType)
LUAU_FASTFLAGVARIABLE(LuauDfgIfBlocksShouldRespectControlFlow)
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,8 +40,19 @@ struct PushScope
~PushScope() ~PushScope()
{ {
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(); stack.pop_back();
} }
else
{
stack.pop_back();
}
}
}; };
const RefinementKey* RefinementKeyArena::leaf(DefId def) const RefinementKey* RefinementKeyArena::leaf(DefId def)
@ -82,12 +86,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 +199,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 +239,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 +254,10 @@ DfgScope* DataFlowGraphBuilder::currentScope()
DfgScope* DataFlowGraphBuilder::makeChildScope(DfgScope::ScopeType scopeType) DfgScope* DataFlowGraphBuilder::makeChildScope(DfgScope::ScopeType scopeType)
{ {
if (FFlag::LuauDfgScopeStackNotNull)
return scopes.emplace_back(new DfgScope{currentScope(), scopeType}).get(); 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 +332,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 +359,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 +377,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 +387,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 +395,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 +413,10 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatBlock* b)
cf = visitBlockWithoutChildScope(b); cf = visitBlockWithoutChildScope(b);
} }
if (FFlag::LuauDfgScopeStackNotNull)
currentScope()->inherit(child); currentScope()->inherit(child);
else
currentScope_DEPRECATED()->inherit(child);
return cf; return cf;
} }
@ -455,7 +473,7 @@ ControlFlow DataFlowGraphBuilder::visit(AstStat* s)
return visit(d); return visit(d);
else if (auto d = s->as<AstStatDeclareFunction>()) else if (auto d = s->as<AstStatDeclareFunction>())
return visit(d); return visit(d);
else if (auto d = s->as<AstStatDeclareClass>()) else if (auto d = s->as<AstStatDeclareExternType>())
return visit(d); return visit(d);
else if (auto error = s->as<AstStatError>()) else if (auto error = s->as<AstStatError>())
return visit(error); return visit(error);
@ -483,13 +501,27 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatIf* i)
elsecf = visit(i->elsebody); elsecf = visit(i->elsebody);
} }
DfgScope* scope = currentScope(); DfgScope* scope = FFlag::LuauDfgScopeStackNotNull ? currentScope() : currentScope_DEPRECATED();
if (FFlag::LuauDfgIfBlocksShouldRespectControlFlow)
{
// If the control flow from the `if` or `else` block is non-linear,
// then we should assume that the _other_ branch is the one taken.
if (thencf != ControlFlow::None && elsecf == ControlFlow::None)
scope->inherit(elseScope);
else if (thencf == ControlFlow::None && elsecf != ControlFlow::None)
scope->inherit(thenScope);
else if ((thencf | elsecf) == ControlFlow::None)
join(scope, thenScope, elseScope);
}
else
{
if (thencf != ControlFlow::None && elsecf == ControlFlow::None) if (thencf != ControlFlow::None && elsecf == ControlFlow::None)
join(scope, scope, elseScope); join(scope, scope, elseScope);
else if (thencf == ControlFlow::None && elsecf != ControlFlow::None) else if (thencf == ControlFlow::None && elsecf != ControlFlow::None)
join(scope, thenScope, scope); join(scope, thenScope, scope);
else if ((thencf | elsecf) == ControlFlow::None) else if ((thencf | elsecf) == ControlFlow::None)
join(scope, thenScope, elseScope); join(scope, thenScope, elseScope);
}
if (thencf == elsecf) if (thencf == elsecf)
return thencf; return thencf;
@ -510,7 +542,10 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatWhile* w)
visit(w->body); visit(w->body);
} }
if (FFlag::LuauDfgScopeStackNotNull)
currentScope()->inherit(whileScope); currentScope()->inherit(whileScope);
else
currentScope_DEPRECATED()->inherit(whileScope);
return ControlFlow::None; return ControlFlow::None;
} }
@ -526,7 +561,10 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatRepeat* r)
visitExpr(r->condition); visitExpr(r->condition);
} }
if (FFlag::LuauDfgScopeStackNotNull)
currentScope()->inherit(repeatScope); currentScope()->inherit(repeatScope);
else
currentScope_DEPRECATED()->inherit(repeatScope);
return ControlFlow::None; return ControlFlow::None;
} }
@ -575,7 +613,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 +623,10 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatLocal* l)
} }
} }
graph.localDefs[local] = def; graph.localDefs[local] = def;
if (FFlag::LuauDfgScopeStackNotNull)
currentScope()->bindings[local] = def; currentScope()->bindings[local] = def;
else
currentScope_DEPRECATED()->bindings[local] = def;
captures[local].allVersions.push_back(def); captures[local].allVersions.push_back(def);
} }
@ -607,16 +648,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;
if (FFlag::LuauDfgScopeStackNotNull)
currentScope()->bindings[f->var] = def; 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);
} }
if (FFlag::LuauDfgScopeStackNotNull)
currentScope()->inherit(forScope); currentScope()->inherit(forScope);
else
currentScope_DEPRECATED()->inherit(forScope);
return ControlFlow::None; return ControlFlow::None;
} }
@ -633,9 +680,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;
if (FFlag::LuauDfgScopeStackNotNull)
currentScope()->bindings[local] = def; currentScope()->bindings[local] = def;
else
currentScope_DEPRECATED()->bindings[local] = def;
captures[local].allVersions.push_back(def); captures[local].allVersions.push_back(def);
} }
@ -646,8 +696,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 +714,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 +740,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 +760,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;
if (FFlag::LuauDfgScopeStackNotNull)
currentScope()->bindings[l->name] = def; 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 +796,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;
if (FFlag::LuauDfgScopeStackNotNull)
currentScope()->bindings[d->name] = def; 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 +811,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;
if (FFlag::LuauDfgScopeStackNotNull)
currentScope()->bindings[d->name] = def; 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();
@ -764,12 +825,15 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatDeclareFunction* d)
visitGenerics(d->generics); visitGenerics(d->generics);
visitGenericPacks(d->genericPacks); visitGenericPacks(d->genericPacks);
visitTypeList(d->params); visitTypeList(d->params);
visitTypeList(d->retTypes); if (FFlag::LuauStoreReturnTypesAsPackOnAst)
visitTypePack(d->retTypes);
else
visitTypeList(d->retTypes_DEPRECATED);
return ControlFlow::None; return ControlFlow::None;
} }
ControlFlow DataFlowGraphBuilder::visit(AstStatDeclareClass* d) ControlFlow DataFlowGraphBuilder::visit(AstStatDeclareExternType* d)
{ {
// This declaration does not "introduce" any bindings in value namespace, // This declaration does not "introduce" any bindings in value namespace,
// so there's no symbolic value to begin with. We'll traverse the properties // so there's no symbolic value to begin with. We'll traverse the properties
@ -777,7 +841,7 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatDeclareClass* d)
DfgScope* unreachable = makeChildScope(); DfgScope* unreachable = makeChildScope();
PushScope ps{scopeStack, unreachable}; PushScope ps{scopeStack, unreachable};
for (AstDeclaredClassProp prop : d->props) for (AstDeclaredExternTypeProperty prop : d->props)
visitType(prop.ty); visitType(prop.ty);
return ControlFlow::None; return ControlFlow::None;
@ -810,19 +874,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 +927,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 +942,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,8 +978,11 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprCall* c)
visitLValue(firstArg, def); visitLValue(firstArg, def);
} }
if (!FFlag::LuauPreprocessTypestatedArgument)
{
for (AstExpr* arg : c->args) for (AstExpr* arg : c->args)
visitExpr(arg); visitExpr(arg);
}
// We treat function calls as "subscripted" as they could potentially // We treat function calls as "subscripted" as they could potentially
// return a subscripted value, consider: // return a subscripted value, consider:
@ -921,7 +994,7 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprCall* c)
// local v = foo({}) // local v = foo({})
// //
// We want to consider `v` to be subscripted here. // We want to consider `v` to be subscripted here.
return {defArena->freshCell(/*subscripted=*/true)}; return {defArena->freshCell(Symbol{}, c->location, /*subscripted=*/true)};
} }
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprIndexName* i) DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprIndexName* i)
@ -929,7 +1002,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)};
} }
@ -942,11 +1015,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)
@ -959,7 +1032,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);
@ -970,7 +1043,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);
@ -979,8 +1052,16 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprFunction* f)
if (f->varargAnnotation) if (f->varargAnnotation)
visitTypePack(f->varargAnnotation); visitTypePack(f->varargAnnotation);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
if (f->returnAnnotation) if (f->returnAnnotation)
visitTypeList(*f->returnAnnotation); visitTypePack(f->returnAnnotation);
}
else
{
if (f->returnAnnotation_DEPRECATED)
visitTypeList(*f->returnAnnotation_DEPRECATED);
}
// TODO: function body can be re-entrant, as in mutations that occurs at the end of the function can also be // TODO: function body can be re-entrant, as in mutations that occurs at the end of the function can also be
// visible to the beginning of the function, so statically speaking, the body of the function has an exit point // visible to the beginning of the function, so statically speaking, the body of the function has an exit point
@ -992,13 +1073,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);
if (FFlag::LuauDfgScopeStackNotNull)
currentScope()->props[tableCell] = {}; 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);
@ -1006,7 +1090,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>())
{
if (FFlag::LuauDfgScopeStackNotNull)
currentScope()->props[tableCell][string->value.data] = result.def; currentScope()->props[tableCell][string->value.data] = result.def;
else
currentScope_DEPRECATED()->props[tableCell][string->value.data] = result.def;
}
} }
} }
@ -1017,7 +1106,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)
@ -1025,7 +1114,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)
@ -1042,7 +1131,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)
@ -1050,7 +1139,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)
@ -1061,7 +1150,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)
@ -1087,12 +1176,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) && !(FFlag::LuauDoNotAddUpvalueTypesToLocalType && l->upvalue))
{ {
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;
@ -1103,12 +1192,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;
@ -1121,10 +1210,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;
} }
@ -1137,12 +1226,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;
} }
@ -1150,7 +1239,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)
@ -1168,6 +1257,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>())
@ -1212,7 +1303,10 @@ void DataFlowGraphBuilder::visitType(AstTypeFunction* f)
visitGenerics(f->generics); visitGenerics(f->generics);
visitGenericPacks(f->genericPacks); visitGenericPacks(f->genericPacks);
visitTypeList(f->argTypes); visitTypeList(f->argTypes);
visitTypeList(f->returnTypes); if (FFlag::LuauStoreReturnTypesAsPackOnAst)
visitTypePack(f->returnTypes);
else
visitTypeList(f->returnTypes_DEPRECATED);
} }
void DataFlowGraphBuilder::visitType(AstTypeTypeof* t) void DataFlowGraphBuilder::visitType(AstTypeTypeof* t)

View file

@ -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

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

View file

@ -1,7 +1,8 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/BuiltinDefinitions.h" #include "Luau/BuiltinDefinitions.h"
LUAU_FASTFLAGVARIABLE(LuauDebugInfoDefn) LUAU_FASTFLAG(LuauDeclareExternType)
LUAU_FASTFLAG(LuauTypeFunOptional)
namespace Luau namespace Luau
{ {
@ -215,15 +216,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: {
@ -270,7 +262,37 @@ declare buffer: {
)BUILTIN_SRC"; )BUILTIN_SRC";
static const std::string kBuiltinDefinitionVectorSrc = R"BUILTIN_SRC( static const std::string kBuiltinDefinitionVectorSrc = (FFlag::LuauDeclareExternType)
? R"BUILTIN_SRC(
-- While vector would have been better represented as a built-in primitive type, type solver extern type handling covers most of the properties
declare extern type vector with
x: number
y: number
z: number
end
declare vector: {
create: @checked (x: number, y: number, z: number?) -> vector,
magnitude: @checked (vec: vector) -> number,
normalize: @checked (vec: vector) -> vector,
cross: @checked (vec1: vector, vec2: vector) -> vector,
dot: @checked (vec1: vector, vec2: vector) -> number,
angle: @checked (vec1: vector, vec2: vector, axis: vector?) -> number,
floor: @checked (vec: vector) -> vector,
ceil: @checked (vec: vector) -> vector,
abs: @checked (vec: vector) -> vector,
sign: @checked (vec: vector) -> vector,
clamp: @checked (vec: vector, min: vector, max: vector) -> vector,
max: @checked (vector, ...vector) -> vector,
min: @checked (vector, ...vector) -> vector,
zero: vector,
one: vector,
}
)BUILTIN_SRC"
: R"BUILTIN_SRC(
-- While vector would have been better represented as a built-in primitive type, type solver class handling covers most of the properties -- While vector would have been better represented as a built-in primitive type, type solver class handling covers most of the properties
declare class vector declare class vector
@ -309,7 +331,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 +339,119 @@ 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 kBuiltinDefinitionTypeMethodSrc = R"BUILTIN_SRC(
export type type = {
tag: "nil" | "unknown" | "never" | "any" | "boolean" | "number" | "string" | "buffer" | "thread" |
"singleton" | "negation" | "union" | "intersection" | "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,
}
)BUILTIN_SRC";
static const std::string kBuiltinDefinitionTypesLibSrc = R"BUILTIN_SRC(
declare types: {
unknown: type,
never: type,
any: type,
boolean: type,
number: type,
string: type,
thread: type,
buffer: type,
singleton: @checked (arg: string | boolean | nil) -> type,
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";
static const std::string kBuiltinDefinitionTypesLibWithOptionalSrc = R"BUILTIN_SRC(
declare types: {
unknown: type,
never: type,
any: type,
boolean: type,
number: type,
string: type,
thread: type,
buffer: type,
singleton: @checked (arg: string | boolean | nil) -> type,
optional: @checked (arg: type) -> type,
generic: @checked (name: string, ispack: boolean?) -> type,
negationof: @checked (arg: type) -> type,
unionof: @checked (...type) -> type,
intersectionof: @checked (...type) -> type,
newtable: @checked (props: {[type]: type} | {[type]: { read: type, write: type } } | nil, indexer: { index: type, readresult: type, writeresult: type }?, metatable: type?) -> type,
newfunction: @checked (parameters: { head: {type}?, tail: type? }?, returns: { head: {type}?, tail: type? }?, generics: {type}?) -> type,
copy: @checked (arg: type) -> type,
}
)BUILTIN_SRC";
std::string getTypeFunctionDefinitionSource()
{
std::string result = kBuiltinDefinitionTypeMethodSrc;
if (FFlag::LuauTypeFunOptional)
result += kBuiltinDefinitionTypesLibWithOptionalSrc;
else
result += kBuiltinDefinitionTypesLibSrc;
return result;
}
} // namespace Luau } // namespace Luau

View file

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

View file

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

View file

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

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,20 +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_FASTFLAG(DebugLuauGreedyGeneralization)
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson) LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson)
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJsonFile) LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJsonFile)
LUAU_FASTFLAGVARIABLE(DebugLuauForbidInternalTypes) LUAU_FASTFLAGVARIABLE(DebugLuauForbidInternalTypes)
LUAU_FASTFLAGVARIABLE(DebugLuauForceStrictMode) LUAU_FASTFLAGVARIABLE(DebugLuauForceStrictMode)
LUAU_FASTFLAGVARIABLE(DebugLuauForceNonStrictMode) LUAU_FASTFLAGVARIABLE(DebugLuauForceNonStrictMode)
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauRunCustomModuleChecks, false)
LUAU_FASTFLAGVARIABLE(LuauModuleHoldsAstRoot) LUAU_FASTFLAG(LuauTypeFunResultInAutocomplete)
LUAU_FASTFLAGVARIABLE(LuauFixMultithreadTypecheck)
LUAU_FASTFLAG(StudioReportLuauAny2)
LUAU_FASTFLAGVARIABLE(LuauSelectivelyRetainDFGArena)
namespace Luau namespace Luau
{ {
@ -135,9 +129,9 @@ static void generateDocumentationSymbols(TypeId ty, const std::string& rootName)
prop.documentationSymbol = rootName + "." + name; prop.documentationSymbol = rootName + "." + name;
} }
} }
else if (ClassType* ctv = getMutable<ClassType>(ty)) else if (ExternType* etv = getMutable<ExternType>(ty))
{ {
for (auto& [name, prop] : ctv->props) for (auto& [name, prop] : etv->props)
{ {
prop.documentationSymbol = rootName + "." + name; prop.documentationSymbol = rootName + "." + name;
} }
@ -461,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;
@ -496,11 +476,6 @@ std::vector<ModuleName> Frontend::checkQueuedModules(
std::function<bool(size_t done, size_t total)> progress std::function<bool(size_t done, size_t total)> progress
) )
{ {
if (!FFlag::LuauFixMultithreadTypecheck)
{
return checkQueuedModules_DEPRECATED(optionOverride, executeTask, progress);
}
FrontendOptions frontendOptions = optionOverride.value_or(options); FrontendOptions frontendOptions = optionOverride.value_or(options);
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
frontendOptions.forAutocomplete = false; frontendOptions.forAutocomplete = false;
@ -685,247 +660,6 @@ std::vector<ModuleName> Frontend::checkQueuedModules(
return checkedModules; return checkedModules;
} }
std::vector<ModuleName> Frontend::checkQueuedModules_DEPRECATED(
std::optional<FrontendOptions> optionOverride,
std::function<void(std::function<void()> task)> executeTask,
std::function<bool(size_t done, size_t total)> progress
)
{
LUAU_ASSERT(!FFlag::LuauFixMultithreadTypecheck);
FrontendOptions frontendOptions = optionOverride.value_or(options);
if (FFlag::LuauSolverV2)
frontendOptions.forAutocomplete = false;
// By taking data into locals, we make sure queue is cleared at the end, even if an ICE or a different exception is thrown
std::vector<ModuleName> currModuleQueue;
std::swap(currModuleQueue, moduleQueue);
DenseHashSet<Luau::ModuleName> seen{{}};
std::vector<BuildQueueItem> buildQueueItems;
for (const ModuleName& name : currModuleQueue)
{
if (seen.contains(name))
continue;
if (!isDirty(name, frontendOptions.forAutocomplete))
{
seen.insert(name);
continue;
}
std::vector<ModuleName> queue;
bool cycleDetected = parseGraph(
queue,
name,
frontendOptions.forAutocomplete,
[&seen](const ModuleName& name)
{
return seen.contains(name);
}
);
addBuildQueueItems(buildQueueItems, queue, cycleDetected, seen, frontendOptions);
}
if (buildQueueItems.empty())
return {};
// We need a mapping from modules to build queue slots
std::unordered_map<ModuleName, size_t> moduleNameToQueue;
for (size_t i = 0; i < buildQueueItems.size(); i++)
{
BuildQueueItem& item = buildQueueItems[i];
moduleNameToQueue[item.name] = i;
}
// Default task execution is single-threaded and immediate
if (!executeTask)
{
executeTask = [](std::function<void()> task)
{
task();
};
}
std::mutex mtx;
std::condition_variable cv;
std::vector<size_t> readyQueueItems;
size_t processing = 0;
size_t remaining = buildQueueItems.size();
auto itemTask = [&](size_t i)
{
BuildQueueItem& item = 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)
{
if (auto it = sourceNodes.find(dep); it != sourceNodes.end())
{
if (it->second->hasDirtyModule(frontendOptions.forAutocomplete))
{
item.dirtyDependencies++;
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
if (processing == 0)
sendCycleItemTask();
std::vector<size_t> nextItems;
std::optional<size_t> itemWithException;
bool cancelled = false;
while (remaining != 0)
{
{
std::unique_lock guard(mtx);
// If nothing is ready yet, wait
cv.wait(
guard,
[&readyQueueItems]
{
return !readyQueueItems.empty();
}
);
// Handle checked items
for (size_t i : readyQueueItems)
{
const BuildQueueItem& item = buildQueueItems[i];
// If exception was thrown, stop adding new items and wait for processing items to complete
if (item.exception)
itemWithException = i;
if (item.module && item.module->cancelled)
cancelled = true;
if (itemWithException || cancelled)
break;
recordItemResult(item);
// Notify items that were waiting for this dependency
for (size_t reverseDep : item.reverseDeps)
{
BuildQueueItem& reverseDepItem = buildQueueItems[reverseDep];
LUAU_ASSERT(reverseDepItem.dirtyDependencies != 0);
reverseDepItem.dirtyDependencies--;
// In case of a module cycle earlier, check if unlocked an item that was already processed
if (!reverseDepItem.processing && reverseDepItem.dirtyDependencies == 0)
nextItems.push_back(reverseDep);
}
}
LUAU_ASSERT(processing >= readyQueueItems.size());
processing -= readyQueueItems.size();
LUAU_ASSERT(remaining >= readyQueueItems.size());
remaining -= readyQueueItems.size();
readyQueueItems.clear();
}
if (progress)
{
if (!progress(buildQueueItems.size() - remaining, buildQueueItems.size()))
cancelled = true;
}
// Items cannot be submitted while holding the lock
for (size_t i : nextItems)
sendItemTask(i);
nextItems.clear();
if (processing == 0)
{
// Typechecking might have been cancelled by user, don't return partial results
if (cancelled)
return {};
// We might have stopped because of a pending exception
if (itemWithException)
recordItemResult(buildQueueItems[*itemWithException]);
}
// If we aren't done, but don't have anything processing, we hit a cycle
if (remaining != 0 && processing == 0)
sendCycleItemTask();
}
std::vector<ModuleName> checkedModules;
checkedModules.reserve(buildQueueItems.size());
for (size_t i = 0; i < buildQueueItems.size(); i++)
checkedModules.push_back(std::move(buildQueueItems[i].name));
return checkedModules;
}
std::optional<CheckResult> Frontend::getCheckResult(const ModuleName& name, bool accumulateNested, bool forAutocomplete) std::optional<CheckResult> Frontend::getCheckResult(const ModuleName& name, bool accumulateNested, bool forAutocomplete)
{ {
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
@ -1217,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;
@ -1237,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)
@ -1269,11 +1003,8 @@ void Frontend::checkBuildQueueItem(BuildQueueItem& item)
freeze(module->interfaceTypes); freeze(module->interfaceTypes);
module->internalTypes.clear(); module->internalTypes.clear();
if (FFlag::LuauSelectivelyRetainDFGArena)
{
module->defArena.allocator.clear(); module->defArena.allocator.clear();
module->keyArena.allocator.clear(); module->keyArena.allocator.clear();
}
module->astTypes.clear(); module->astTypes.clear();
module->astTypePacks.clear(); module->astTypePacks.clear();
@ -1367,6 +1098,19 @@ void Frontend::performQueueItemTask(std::shared_ptr<BuildQueueWorkState> state,
{ {
BuildQueueItem& item = state->buildQueueItems[itemPos]; BuildQueueItem& item = state->buildQueueItems[itemPos];
if (DFFlag::LuauRethrowKnownExceptions)
{
try
{
checkBuildQueueItem(item);
}
catch (const Luau::InternalCompilerError&)
{
item.exception = std::current_exception();
}
}
else
{
try try
{ {
checkBuildQueueItem(item); checkBuildQueueItem(item);
@ -1375,6 +1119,7 @@ void Frontend::performQueueItemTask(std::shared_ptr<BuildQueueWorkState> state,
{ {
item.exception = std::current_exception(); item.exception = std::current_exception();
} }
}
{ {
std::unique_lock guard(state->mtx); std::unique_lock guard(state->mtx);
@ -1532,6 +1277,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,
@ -1548,6 +1294,7 @@ ModulePtr check(
moduleResolver, moduleResolver,
fileResolver, fileResolver,
parentScope, parentScope,
typeFunctionScope,
std::move(prepareModuleScope), std::move(prepareModuleScope),
options, options,
limits, limits,
@ -1558,7 +1305,7 @@ ModulePtr check(
struct InternalTypeFinder : TypeOnceVisitor struct InternalTypeFinder : TypeOnceVisitor
{ {
bool visit(TypeId, const ClassType&) override bool visit(TypeId, const ExternType&) override
{ {
return false; return false;
} }
@ -1609,6 +1356,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,
@ -1629,7 +1377,6 @@ 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;
if (FFlag::LuauModuleHoldsAstRoot)
result->root = sourceModule.root; result->root = sourceModule.root;
iceHandler->moduleName = sourceModule.name; iceHandler->moduleName = sourceModule.name;
@ -1655,7 +1402,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,
@ -1666,16 +1413,43 @@ ModulePtr check(
builtinTypes, builtinTypes,
iceHandler, iceHandler,
parentScope, parentScope,
typeFunctionScope,
std::move(prepareModuleScope), std::move(prepareModuleScope),
logger.get(), logger.get(),
NotNull{&dfg}, NotNull{&dfg},
requireCycles requireCycles
}; };
// FIXME: Delete this flag when clipping FFlag::DebugLuauGreedyGeneralization.
//
// This optional<> only exists so that we can run one constructor when the flag
// is set, and another when it is unset.
std::optional<ConstraintSolver> cs;
if (FFlag::DebugLuauGreedyGeneralization)
{
ConstraintSet constraintSet = cg.run(sourceModule.root);
result->errors = std::move(constraintSet.errors);
cs.emplace(
NotNull{&normalizer},
NotNull{simplifier.get()},
NotNull{&typeFunctionRuntime},
result->name,
moduleResolver,
requireCycles,
logger.get(),
NotNull{&dfg},
limits,
std::move(constraintSet)
);
}
else
{
cg.visitModuleRoot(sourceModule.root); cg.visitModuleRoot(sourceModule.root);
result->errors = std::move(cg.errors); result->errors = std::move(cg.errors);
ConstraintSolver cs{ cs.emplace(
NotNull{&normalizer}, NotNull{&normalizer},
NotNull{simplifier.get()}, NotNull{simplifier.get()},
NotNull{&typeFunctionRuntime}, NotNull{&typeFunctionRuntime},
@ -1688,14 +1462,17 @@ ModulePtr check(
logger.get(), logger.get(),
NotNull{&dfg}, NotNull{&dfg},
limits limits
}; );
}
LUAU_ASSERT(bool(cs));
if (options.randomizeConstraintResolutionSeed) if (options.randomizeConstraintResolutionSeed)
cs.randomize(*options.randomizeConstraintResolutionSeed); cs->randomize(*options.randomizeConstraintResolutionSeed);
try try
{ {
cs.run(); cs->run();
} }
catch (const TimeLimitError&) catch (const TimeLimitError&)
{ {
@ -1715,12 +1492,12 @@ ModulePtr check(
printf("%s\n", output.c_str()); printf("%s\n", output.c_str());
} }
for (TypeError& e : cs.errors) for (TypeError& e : cs->errors)
result->errors.emplace_back(std::move(e)); result->errors.emplace_back(std::move(e));
result->scopes = std::move(cg.scopes); result->scopes = std::move(cg.scopes);
result->type = sourceModule.type; result->type = sourceModule.type;
result->upperBoundContributors = std::move(cs.upperBoundContributors); result->upperBoundContributors = std::move(cs->upperBoundContributors);
if (result->timeout || result->cancelled) if (result->timeout || result->cancelled)
{ {
@ -1848,6 +1625,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,

View file

@ -4,19 +4,85 @@
#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/Substitution.h"
#include "Luau/VisitType.h" #include "Luau/VisitType.h"
LUAU_FASTFLAG(LuauAutocompleteRefactorsForIncrementalAutocomplete) LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
LUAU_FASTFLAGVARIABLE(LuauGeneralizationRemoveRecursiveUpperBound2)
LUAU_FASTFLAGVARIABLE(LuauNonReentrantGeneralization2)
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 +96,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 +103,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 +112,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 +162,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 +209,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 +337,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,7 +364,6 @@ 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 +403,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 +426,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 +434,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 +443,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,7 +469,25 @@ 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::LuauNonReentrantGeneralization2)
{
if (!subsumes(scope, ft.scope))
return true;
GeneralizationParams<TypeId>& params = types[ty];
++params.useCount;
if (cachedTypes->contains(ty) || seenWithCurrentPolarity(ty))
return false;
if (!isWithinFunction)
params.foundOutsideFunctions = true;
params.polarity |= polarity;
}
else
{
if (cachedTypes->contains(ty) || seenWithCurrentPolarity(ty))
return false; return false;
if (!subsumes(scope, ft.scope)) if (!subsumes(scope, ft.scope))
@ -418,16 +495,19 @@ 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;
@ -435,23 +515,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))
{
if (FFlag::LuauNonReentrantGeneralization2)
unsealedTables.insert(ty);
else
{ {
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");
}
} }
} }
@ -461,10 +548,10 @@ struct FreeTypeSearcher : TypeVisitor
traverse(*prop.readTy); traverse(*prop.readTy);
else else
{ {
LUAU_ASSERT(prop.isShared() || FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete); LUAU_ASSERT(prop.isShared());
Polarity p = polarity; Polarity p = polarity;
polarity = Both; polarity = Polarity::Mixed;
traverse(prop.type()); traverse(prop.type());
polarity = p; polarity = p;
} }
@ -472,8 +559,27 @@ struct FreeTypeSearcher : TypeVisitor
if (tt.indexer) if (tt.indexer)
{ {
if (FFlag::LuauNonReentrantGeneralization2)
{
// {[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->indexType);
traverse(tt.indexer->indexResultType); traverse(tt.indexer->indexResultType);
polarity = p;
}
else
{
traverse(tt.indexer->indexType);
traverse(tt.indexer->indexResultType);
}
} }
return false; return false;
@ -481,43 +587,63 @@ 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;
} }
bool visit(TypeId, const ClassType&) override bool visit(TypeId, const ExternType&) override
{ {
return false; return false;
} }
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;
if (FFlag::LuauNonReentrantGeneralization2)
{
GeneralizationParams<TypePackId>& params = typePacks[tp];
++params.useCount;
if (!isWithinFunction)
params.foundOutsideFunctions = true;
params.polarity |= polarity;
}
else
{
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;
@ -547,7 +673,7 @@ struct TypeCacher : TypeOnceVisitor
{ {
} }
void cache(TypeId ty) void cache(TypeId ty) const
{ {
cachedTypes->insert(ty); cachedTypes->insert(ty);
} }
@ -770,7 +896,7 @@ struct TypeCacher : TypeOnceVisitor
return false; return false;
} }
bool visit(TypeId ty, const ClassType&) override bool visit(TypeId ty, const ExternType&) override
{ {
cache(ty); cache(ty);
return false; return false;
@ -967,13 +1093,277 @@ struct TypeCacher : TypeOnceVisitor
} }
}; };
struct RemoveType : Substitution // NOLINT
{
NotNull<BuiltinTypes> builtinTypes;
TypeId needle;
RemoveType(NotNull<BuiltinTypes> builtinTypes, TypeArena* arena, TypeId needle)
: Substitution(arena)
, builtinTypes(builtinTypes)
, needle(needle)
{
}
bool ignoreChildren(TypeId ty) override
{
if (get<UnionType>(ty) || get<IntersectionType>(ty))
return false;
else
return true;
}
bool isDirty(TypeId ty) override
{
// A union or intersection is dirty if it contains the needle or if it has any duplicate members.
if (auto ut = get<UnionType>(ty))
{
DenseHashSet<TypeId> distinctParts{nullptr};
size_t count = 0;
for (TypeId part : ut)
{
++count;
if (part == needle)
return true;
distinctParts.insert(follow(part));
}
return distinctParts.size() != count;
}
else if (auto it = get<IntersectionType>(ty))
{
DenseHashSet<TypeId> distinctParts{nullptr};
size_t count = 0;
for (TypeId part : it)
{
++count;
if (part == needle)
return true;
distinctParts.insert(follow(part));
}
return distinctParts.size() != count;
}
return false;
}
bool isDirty(TypePackId tp) override
{
return false;
}
TypeId clean(TypeId ty) override
{
if (auto ut = get<UnionType>(ty))
{
OrderedSet<TypeId> newParts;
for (TypeId ty : ut)
{
if (ty != needle)
newParts.insert(ty);
}
if (newParts.empty())
return builtinTypes->neverType;
else if (newParts.size() == 1)
{
TypeId onlyType = *newParts.begin();
LUAU_ASSERT(onlyType != needle);
return onlyType;
}
else
return arena->addType(UnionType{newParts.takeVector()});
}
else if (auto it = get<IntersectionType>(ty))
{
OrderedSet<TypeId> newParts;
for (TypeId ty : it)
{
if (ty != needle)
newParts.insert(ty);
}
if (newParts.empty())
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()});
}
else
return ty;
}
TypePackId clean(TypePackId tp) override
{
return tp;
}
};
/**
* Remove occurrences of `needle` within `haystack`. This is used to cull cyclic bounds from free types.
*
* @param haystack Either the upper or lower bound of a free type.
* @param needle The type to be removed.
*/
[[nodiscard]]
static std::optional<
TypeId> removeType(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, TypeId haystack, TypeId needle)
{
RemoveType rt{builtinTypes, arena, needle};
return rt.substitute(haystack);
}
GeneralizationResult<TypeId> generalizeType(
NotNull<TypeArena> arena,
NotNull<BuiltinTypes> builtinTypes,
NotNull<Scope> scope,
TypeId freeTy,
const GeneralizationParams<TypeId>& params
)
{
freeTy = follow(freeTy);
FreeType* ft = getMutable<FreeType>(freeTy);
LUAU_ASSERT(ft);
LUAU_ASSERT(isKnown(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 (!isWithinFunction || (!FFlag::DebugLuauGreedyGeneralization && (params.polarity != Polarity::Mixed && params.useCount == 1)))
emplaceType<BoundType>(asMutable(freeTy), builtinTypes->unknownType);
else
{
emplaceType<GenericType>(asMutable(freeTy), scope, params.polarity);
return {freeTy, /*wasReplacedByGeneric*/ true};
}
}
// 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
{
std::optional<TypeId> removed = removeType(arena, builtinTypes, lb, freeTy);
if (removed)
lb = *removed;
else
return {std::nullopt, false, /*resourceLimitsExceeded*/ true};
ft->lowerBound = lb;
}
if (follow(lb) != freeTy)
emplaceType<BoundType>(asMutable(freeTy), lb);
else if (!isWithinFunction || (!FFlag::DebugLuauGreedyGeneralization && 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, /*wasReplacedByGeneric*/ true};
}
}
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.
std::optional<TypeId> removed = removeType(arena, builtinTypes, ub, freeTy);
if (removed)
ub = *removed;
else
return {std::nullopt, false, /*resourceLimitsExceeded*/ true};
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, /*wasReplacedByGeneric*/ true};
}
}
return {freeTy, /*wasReplacedByGeneric*/ false};
}
GeneralizationResult<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 {tp, /*wasReplacedByGeneric*/ false};
const FreeTypePack* ftp = get<FreeTypePack>(tp);
if (!ftp)
return {tp, /*wasReplacedByGeneric*/ false};
if (!subsumes(scope, ftp->scope))
return {tp, /*wasReplacedByGeneric*/ false};
if (1 == params.useCount)
emplaceTypePack<BoundTypePack>(asMutable(tp), builtinTypes->unknownTypePack);
else
{
emplaceTypePack<GenericTypePack>(asMutable(tp), scope, params.polarity);
return {tp, /*wasReplacedByGeneric*/ true};
}
return {tp, /*wasReplacedByGeneric*/ false};
}
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 std::optional<TypeId> generalizationTarget
) )
{ {
ty = follow(ty); ty = follow(ty);
@ -984,7 +1374,61 @@ 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::LuauNonReentrantGeneralization2)
{
FunctionType* functionTy = getMutable<FunctionType>(ty);
auto pushGeneric = [&](TypeId t)
{
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 (!generalizationTarget || freeTy == *generalizationTarget)
{
GeneralizationResult<TypeId> res = generalizeType(arena, builtinTypes, scope, freeTy, params);
if (res.resourceLimitsExceeded)
return std::nullopt;
if (res && res.wasReplacedByGeneric)
pushGeneric(*res.result);
}
}
for (TypeId unsealedTableTy : fts.unsealedTables)
{
if (!generalizationTarget || unsealedTableTy == *generalizationTarget)
sealTable(scope, unsealedTableTy);
}
for (const auto& [freePackId, params] : fts.typePacks)
{
TypePackId freePack = follow(freePackId);
if (!generalizationTarget)
{
GeneralizationResult<TypePackId> generalizedTp = generalizeTypePack(arena, builtinTypes, scope, freePack, params);
if (generalizedTp.resourceLimitsExceeded)
return std::nullopt;
if (generalizedTp && generalizedTp.wasReplacedByGeneric)
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); gen.traverse(ty);
@ -1015,8 +1459,202 @@ std::optional<TypeId> generalize(
ftv->genericPacks.push_back(gp); ftv->genericPacks.push_back(gp);
} }
} }
}
return ty; return ty;
} }
struct GenericCounter : TypeVisitor
{
struct CounterState
{
size_t count = 0;
Polarity polarity = Polarity::None;
};
NotNull<DenseHashSet<TypeId>> cachedTypes;
DenseHashMap<TypeId, CounterState> generics{nullptr};
DenseHashMap<TypePackId, CounterState> genericPacks{nullptr};
Polarity polarity = Polarity::Positive;
explicit GenericCounter(NotNull<DenseHashSet<TypeId>> cachedTypes)
: cachedTypes(cachedTypes)
{
}
bool visit(TypeId ty, const FunctionType& ft) override
{
if (ty->persistent)
return false;
polarity = invert(polarity);
traverse(ft.argTypes);
polarity = invert(polarity);
traverse(ft.retTypes);
return false;
}
bool visit(TypeId ty, const TableType& tt) override
{
if (ty->persistent)
return false;
const Polarity previous = polarity;
for (const auto& [_name, prop] : tt.props)
{
if (prop.isReadOnly())
traverse(*prop.readTy);
else
{
LUAU_ASSERT(prop.isShared());
polarity = Polarity::Mixed;
traverse(prop.type());
polarity = previous;
}
}
if (tt.indexer)
{
polarity = Polarity::Mixed;
traverse(tt.indexer->indexType);
traverse(tt.indexer->indexResultType);
polarity = previous;
}
return false;
}
bool visit(TypeId ty, const ExternType&) override
{
return false;
}
bool visit(TypeId ty, const GenericType&) override
{
auto state = generics.find(ty);
if (state)
{
++state->count;
state->polarity |= polarity;
}
return false;
}
bool visit(TypePackId tp, const GenericTypePack&) override
{
auto state = genericPacks.find(tp);
if (state)
{
++state->count;
state->polarity |= polarity;
}
return false;
}
};
void pruneUnnecessaryGenerics(
NotNull<TypeArena> arena,
NotNull<BuiltinTypes> builtinTypes,
NotNull<Scope> scope,
NotNull<DenseHashSet<TypeId>> cachedTypes,
TypeId ty
)
{
if (!FFlag::DebugLuauGreedyGeneralization)
return;
ty = follow(ty);
if (ty->owningArena != arena || ty->persistent)
return;
FunctionType* functionTy = getMutable<FunctionType>(ty);
if (!functionTy)
return;
// If a generic has no explicit name and is only referred to in one place in
// the function's signature, it can be replaced with unknown.
GenericCounter counter{cachedTypes};
for (TypeId generic : functionTy->generics)
{
generic = follow(generic);
auto g = get<GenericType>(generic);
if (g && !g->explicitName)
counter.generics[generic] = {};
}
// It is sometimes the case that a pack in the generic list will become a
// pack that (transitively) has a generic tail. If it does, we need to add
// that generic tail to the generic pack list.
for (size_t i = 0; i < functionTy->genericPacks.size(); ++i)
{
TypePackId genericPack = follow(functionTy->genericPacks[i]);
TypePackId tail = getTail(genericPack);
if (tail != genericPack)
functionTy->genericPacks.push_back(tail);
if (auto g = get<GenericTypePack>(tail); g && !g->explicitName)
counter.genericPacks[genericPack] = {};
}
counter.traverse(ty);
for (const auto& [generic, state] : counter.generics)
{
if (state.count == 1 && state.polarity != Polarity::Mixed)
emplaceType<BoundType>(asMutable(generic), builtinTypes->unknownType);
}
// Remove duplicates and types that aren't actually generics.
DenseHashSet<TypeId> seen{nullptr};
auto it = std::remove_if(
functionTy->generics.begin(),
functionTy->generics.end(),
[&](TypeId ty)
{
ty = follow(ty);
if (seen.contains(ty))
return true;
seen.insert(ty);
return !get<GenericType>(ty);
}
);
functionTy->generics.erase(it, functionTy->generics.end());
for (const auto& [genericPack, state] : counter.genericPacks)
{
if (state.count == 1)
emplaceTypePack<BoundTypePack>(asMutable(genericPack), builtinTypes->unknownTypePack);
}
DenseHashSet<TypePackId> seen2{nullptr};
auto it2 = std::remove_if(
functionTy->genericPacks.begin(),
functionTy->genericPacks.end(),
[&](TypePackId tp)
{
tp = follow(tp);
if (seen2.contains(tp))
return true;
seen2.insert(tp);
return !get<GenericTypePack>(tp);
}
);
functionTy->genericPacks.erase(it2, functionTy->genericPacks.end());
}
} // namespace Luau } // namespace Luau

View file

@ -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(LuauNonReentrantGeneralization2)
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)
{
generic = follow(generic);
const auto gen = get<GenericType>(generic);
if (gen && subsumes(scope, gen->scope))
types[generic] = Polarity::None;
}
for (TypePackId genericPack : ft.genericPacks)
{
genericPack = follow(genericPack);
const auto gen = get<GenericTypePack>(genericPack);
if (gen && subsumes(scope, gen->scope))
packs[genericPack] = Polarity::None;
}
flip();
traverse(ft.argTypes);
flip();
traverse(ft.retTypes);
polarity = p;
return false;
}
bool visit(TypeId, const ExternType&) 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::LuauNonReentrantGeneralization2)
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

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

View file

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

View file

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

View file

@ -19,6 +19,9 @@ LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauAttribute) LUAU_FASTFLAG(LuauAttribute)
LUAU_FASTFLAGVARIABLE(LintRedundantNativeAttribute) LUAU_FASTFLAGVARIABLE(LintRedundantNativeAttribute)
LUAU_FASTFLAG(LuauDeprecatedAttribute)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
namespace Luau namespace Luau
{ {
@ -906,6 +909,11 @@ private:
return true; return true;
} }
bool visit(AstTypePack* node) override
{
return FFlag::LuauStoreReturnTypesAsPackOnAst;
}
bool visit(AstTypeReference* node) override bool visit(AstTypeReference* node) override
{ {
if (!node->prefix) if (!node->prefix)
@ -1968,6 +1976,11 @@ private:
return true; return true;
} }
bool visit(AstTypePack* node) override
{
return FFlag::LuauStoreReturnTypesAsPackOnAst;
}
bool visit(AstTypeTable* node) override bool visit(AstTypeTable* node) override
{ {
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
@ -2280,6 +2293,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))
@ -2319,18 +2383,39 @@ private:
void check(AstExprIndexName* node, TypeId ty) void check(AstExprIndexName* node, TypeId ty)
{ {
if (const ClassType* cty = get<ClassType>(ty)) if (const ExternType* cty = get<ExternType>(ty))
{ {
const Property* prop = lookupClassProp(cty, node->index.value); const Property* prop = lookupExternTypeProp(cty, node->index.value);
if (prop && prop->deprecated) if (prop && prop->deprecated)
report(node->location, *prop, cty->name.c_str(), node->index.value); report(node->location, *prop, cty->name.c_str(), node->index.value);
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())
{
if (prop->second.deprecated)
{ {
// strip synthetic typeof() for builtin tables // strip synthetic typeof() for builtin tables
if (tty->name && tty->name->compare(0, 7, "typeof(") == 0 && tty->name->back() == ')') if (tty->name && tty->name->compare(0, 7, "typeof(") == 0 && tty->name->back() == ')')
@ -2338,6 +2423,26 @@ private:
else else
report(node->location, prop->second, tty->name ? tty->name->c_str() : nullptr, node->index.value); 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 +2460,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 +2489,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))
@ -75,12 +60,9 @@ bool isWithinComment(const std::vector<Comment>& commentLocations, Position pos)
commentLocations.end(), commentLocations.end(),
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) if (a.type == Lexeme::Comment)
return a.location.end.line < b.location.end.line; 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,6 +265,9 @@ struct ClonePublicInterface : Substitution
TypeId type = cloneType(tf.type); TypeId type = cloneType(tf.type);
if (FFlag::LuauRetainDefinitionAliasLocations)
return TypeFun{typeParams, typePackParams, type, tf.definitionLocation};
else
return TypeFun{typeParams, typePackParams, type}; 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"
@ -19,9 +20,12 @@
#include <iostream> #include <iostream>
#include <iterator> #include <iterator>
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds) LUAU_FASTFLAG(DebugLuauMagicTypes)
LUAU_FASTFLAGVARIABLE(LuauNonStrictVisitorImprovements) LUAU_FASTFLAGVARIABLE(LuauNonStrictVisitorImprovements)
LUAU_FASTFLAGVARIABLE(LuauNewNonStrictWarnOnUnknownGlobals) LUAU_FASTFLAGVARIABLE(LuauNewNonStrictWarnOnUnknownGlobals)
LUAU_FASTFLAGVARIABLE(LuauNewNonStrictVisitTypes2)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
namespace Luau namespace Luau
{ {
@ -213,7 +217,7 @@ struct NonStrictTypeChecker
return *fst; return *fst;
else if (auto ftp = get<FreeTypePack>(pack)) else if (auto ftp = get<FreeTypePack>(pack))
{ {
TypeId result = FFlag::LuauFreeTypesMustHaveBounds ? arena->freshType(builtinTypes, ftp->scope) : arena->addType(FreeType{ftp->scope}); TypeId result = arena->freshType(builtinTypes, ftp->scope);
TypePackId freeTail = arena->addTypePack(FreeTypePack{ftp->scope}); TypePackId freeTail = arena->addTypePack(FreeTypePack{ftp->scope});
TypePack* resultPack = emplaceTypePack<TypePack>(asMutable(pack)); TypePack* resultPack = emplaceTypePack<TypePack>(asMutable(pack));
@ -307,7 +311,7 @@ struct NonStrictTypeChecker
return visit(s); return visit(s);
else if (auto s = stat->as<AstStatDeclareGlobal>()) else if (auto s = stat->as<AstStatDeclareGlobal>())
return visit(s); return visit(s);
else if (auto s = stat->as<AstStatDeclareClass>()) else if (auto s = stat->as<AstStatDeclareExternType>())
return visit(s); return visit(s);
else if (auto s = stat->as<AstStatError>()) else if (auto s = stat->as<AstStatError>())
return visit(s); return visit(s);
@ -333,7 +337,12 @@ struct NonStrictTypeChecker
// local x ; B generates the context of B without x // local x ; B generates the context of B without x
visit(local); visit(local);
for (auto local : local->vars) for (auto local : local->vars)
{
ctx.remove(dfg->getDef(local)); ctx.remove(dfg->getDef(local));
if (FFlag::LuauNewNonStrictVisitTypes2)
visit(local->annotation);
}
} }
else else
ctx = NonStrictContext::disjunction(builtinTypes, arena, visit(stat), ctx); ctx = NonStrictContext::disjunction(builtinTypes, arena, visit(stat), ctx);
@ -418,6 +427,9 @@ struct NonStrictTypeChecker
NonStrictContext visit(AstStatFor* forStatement) NonStrictContext visit(AstStatFor* forStatement)
{ {
if (FFlag::LuauNewNonStrictVisitTypes2)
visit(forStatement->var->annotation);
if (FFlag::LuauNonStrictVisitorImprovements) if (FFlag::LuauNonStrictVisitorImprovements)
{ {
// TODO: throwing out context based on same principle as existing code? // TODO: throwing out context based on same principle as existing code?
@ -437,6 +449,12 @@ struct NonStrictTypeChecker
NonStrictContext visit(AstStatForIn* forInStatement) NonStrictContext visit(AstStatForIn* forInStatement)
{ {
if (FFlag::LuauNewNonStrictVisitTypes2)
{
for (auto var : forInStatement->vars)
visit(var->annotation);
}
if (FFlag::LuauNonStrictVisitorImprovements) if (FFlag::LuauNonStrictVisitorImprovements)
{ {
for (AstExpr* rhs : forInStatement->values) for (AstExpr* rhs : forInStatement->values)
@ -485,6 +503,12 @@ struct NonStrictTypeChecker
NonStrictContext visit(AstStatTypeAlias* typeAlias) NonStrictContext visit(AstStatTypeAlias* typeAlias)
{ {
if (FFlag::LuauNewNonStrictVisitTypes2)
{
visitGenerics(typeAlias->generics, typeAlias->genericPacks);
visit(typeAlias->type);
}
return {}; return {};
} }
@ -495,16 +519,38 @@ struct NonStrictTypeChecker
NonStrictContext visit(AstStatDeclareFunction* declFn) NonStrictContext visit(AstStatDeclareFunction* declFn)
{ {
if (FFlag::LuauNewNonStrictVisitTypes2)
{
visitGenerics(declFn->generics, declFn->genericPacks);
visit(declFn->params);
visit(declFn->retTypes);
}
return {}; return {};
} }
NonStrictContext visit(AstStatDeclareGlobal* declGlobal) NonStrictContext visit(AstStatDeclareGlobal* declGlobal)
{ {
if (FFlag::LuauNewNonStrictVisitTypes2)
visit(declGlobal->type);
return {}; return {};
} }
NonStrictContext visit(AstStatDeclareClass* declClass) NonStrictContext visit(AstStatDeclareExternType* declClass)
{ {
if (FFlag::LuauNewNonStrictVisitTypes2)
{
if (declClass->indexer)
{
visit(declClass->indexer->indexType);
visit(declClass->indexer->resultType);
}
for (auto prop : declClass->props)
visit(prop.ty);
}
return {}; return {};
} }
@ -763,9 +809,32 @@ struct NonStrictTypeChecker
for (AstLocal* local : exprFn->args) for (AstLocal* local : exprFn->args)
{ {
if (std::optional<TypeId> ty = willRunTimeErrorFunctionDefinition(local, remainder)) if (std::optional<TypeId> ty = willRunTimeErrorFunctionDefinition(local, remainder))
reportError(NonStrictFunctionDefinitionError{exprFn->debugname.value, local->name.value, *ty}, local->location); {
remainder.remove(dfg->getDef(local)); const char* debugname = exprFn->debugname.value;
reportError(NonStrictFunctionDefinitionError{debugname ? debugname : "", local->name.value, *ty}, local->location);
} }
remainder.remove(dfg->getDef(local));
if (FFlag::LuauNewNonStrictVisitTypes2)
visit(local->annotation);
}
if (FFlag::LuauNewNonStrictVisitTypes2)
{
visitGenerics(exprFn->generics, exprFn->genericPacks);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
visit(exprFn->returnAnnotation);
else
{
if (exprFn->returnAnnotation_DEPRECATED)
visit(*exprFn->returnAnnotation_DEPRECATED);
}
if (exprFn->varargAnnotation)
visit(exprFn->varargAnnotation);
}
return remainder; return remainder;
} }
@ -806,6 +875,9 @@ struct NonStrictTypeChecker
NonStrictContext visit(AstExprTypeAssertion* typeAssertion) NonStrictContext visit(AstExprTypeAssertion* typeAssertion)
{ {
if (FFlag::LuauNewNonStrictVisitTypes2)
visit(typeAssertion->annotation);
if (FFlag::LuauNonStrictVisitorImprovements) if (FFlag::LuauNonStrictVisitorImprovements)
return visit(typeAssertion->expr, ValueContext::RValue); return visit(typeAssertion->expr, ValueContext::RValue);
else else
@ -842,6 +914,331 @@ struct NonStrictTypeChecker
return {}; return {};
} }
void visit(AstType* ty)
{
LUAU_ASSERT(FFlag::LuauNewNonStrictVisitTypes2);
// If this node is `nullptr`, early exit.
if (!ty)
return;
if (auto t = ty->as<AstTypeReference>())
return visit(t);
else if (auto t = ty->as<AstTypeTable>())
return visit(t);
else if (auto t = ty->as<AstTypeFunction>())
return visit(t);
else if (auto t = ty->as<AstTypeTypeof>())
return visit(t);
else if (auto t = ty->as<AstTypeUnion>())
return visit(t);
else if (auto t = ty->as<AstTypeIntersection>())
return visit(t);
else if (auto t = ty->as<AstTypeGroup>())
return visit(t->type);
}
void visit(AstTypeReference* ty)
{
// No further validation is necessary in this case. The main logic for
// _luau_print is contained in lookupAnnotation.
if (FFlag::DebugLuauMagicTypes && ty->name == "_luau_print")
return;
for (const AstTypeOrPack& param : ty->parameters)
{
if (param.type)
visit(param.type);
else
visit(param.typePack);
}
Scope* scope = findInnermostScope(ty->location);
LUAU_ASSERT(scope);
std::optional<TypeFun> alias = ty->prefix ? scope->lookupImportedType(ty->prefix->value, ty->name.value) : scope->lookupType(ty->name.value);
if (alias.has_value())
{
size_t typesRequired = alias->typeParams.size();
size_t packsRequired = alias->typePackParams.size();
bool hasDefaultTypes = std::any_of(
alias->typeParams.begin(),
alias->typeParams.end(),
[](auto&& el)
{
return el.defaultValue.has_value();
}
);
bool hasDefaultPacks = std::any_of(
alias->typePackParams.begin(),
alias->typePackParams.end(),
[](auto&& el)
{
return el.defaultValue.has_value();
}
);
if (!ty->hasParameterList)
{
if ((!alias->typeParams.empty() && !hasDefaultTypes) || (!alias->typePackParams.empty() && !hasDefaultPacks))
reportError(GenericError{"Type parameter list is required"}, ty->location);
}
size_t typesProvided = 0;
size_t extraTypes = 0;
size_t packsProvided = 0;
for (const AstTypeOrPack& p : ty->parameters)
{
if (p.type)
{
if (packsProvided != 0)
{
reportError(GenericError{"Type parameters must come before type pack parameters"}, ty->location);
continue;
}
if (typesProvided < typesRequired)
typesProvided += 1;
else
extraTypes += 1;
}
else if (p.typePack)
{
std::optional<TypePackId> tp = lookupPackAnnotation(p.typePack);
if (!tp.has_value())
continue;
if (typesProvided < typesRequired && size(*tp) == 1 && finite(*tp) && first(*tp))
typesProvided += 1;
else
packsProvided += 1;
}
}
if (extraTypes != 0 && packsProvided == 0)
{
// Extra types are only collected into a pack if a pack is expected
if (packsRequired != 0)
packsProvided += 1;
else
typesProvided += extraTypes;
}
for (size_t i = typesProvided; i < typesRequired; ++i)
{
if (alias->typeParams[i].defaultValue)
typesProvided += 1;
}
for (size_t i = packsProvided; i < packsRequired; ++i)
{
if (alias->typePackParams[i].defaultValue)
packsProvided += 1;
}
if (extraTypes == 0 && packsProvided + 1 == packsRequired)
packsProvided += 1;
if (typesProvided != typesRequired || packsProvided != packsRequired)
{
reportError(
IncorrectGenericParameterCount{
/* name */ ty->name.value,
/* typeFun */ *alias,
/* actualParameters */ typesProvided,
/* actualPackParameters */ packsProvided,
},
ty->location
);
}
}
else
{
if (scope->lookupPack(ty->name.value))
{
reportError(
SwappedGenericTypeParameter{
ty->name.value,
SwappedGenericTypeParameter::Kind::Type,
},
ty->location
);
}
else
{
std::string symbol = "";
if (ty->prefix)
{
symbol += (*(ty->prefix)).value;
symbol += ".";
}
symbol += ty->name.value;
reportError(UnknownSymbol{symbol, UnknownSymbol::Context::Type}, ty->location);
}
}
}
void visit(AstTypeTable* table)
{
if (table->indexer)
{
visit(table->indexer->indexType);
visit(table->indexer->resultType);
}
for (auto prop : table->props)
visit(prop.type);
}
void visit(AstTypeFunction* function)
{
visit(function->argTypes);
visit(function->returnTypes);
}
void visit(AstTypeTypeof* typeOf)
{
visit(typeOf->expr, ValueContext::RValue);
}
void visit(AstTypeUnion* unionType)
{
for (auto typ : unionType->types)
visit(typ);
}
void visit(AstTypeIntersection* intersectionType)
{
for (auto typ : intersectionType->types)
visit(typ);
}
void visit(AstTypeList& list)
{
for (auto typ : list.types)
visit(typ);
if (list.tailType)
visit(list.tailType);
}
void visit(AstTypePack* pack)
{
LUAU_ASSERT(FFlag::LuauNewNonStrictVisitTypes2);
// If there is no pack node, early exit.
if (!pack)
return;
if (auto p = pack->as<AstTypePackExplicit>())
return visit(p);
else if (auto p = pack->as<AstTypePackVariadic>())
return visit(p);
else if (auto p = pack->as<AstTypePackGeneric>())
return visit(p);
}
void visit(AstTypePackExplicit* tp)
{
for (AstType* type : tp->typeList.types)
visit(type);
if (tp->typeList.tailType)
visit(tp->typeList.tailType);
}
void visit(AstTypePackVariadic* tp)
{
visit(tp->variadicType);
}
void visit(AstTypePackGeneric* tp)
{
Scope* scope = findInnermostScope(tp->location);
LUAU_ASSERT(scope);
std::optional<TypePackId> alias = scope->lookupPack(tp->genericName.value);
if (!alias.has_value())
{
if (scope->lookupType(tp->genericName.value))
{
reportError(
SwappedGenericTypeParameter{
tp->genericName.value,
SwappedGenericTypeParameter::Kind::Pack,
},
tp->location
);
}
}
else
{
reportError(UnknownSymbol{tp->genericName.value, UnknownSymbol::Context::Type}, tp->location);
}
}
void visitGenerics(AstArray<AstGenericType*> generics, AstArray<AstGenericTypePack*> genericPacks)
{
DenseHashSet<AstName> seen{AstName{}};
for (const auto* g : generics)
{
if (seen.contains(g->name))
reportError(DuplicateGenericParameter{g->name.value}, g->location);
else
seen.insert(g->name);
if (g->defaultValue)
visit(g->defaultValue);
}
for (const auto* g : genericPacks)
{
if (seen.contains(g->name))
reportError(DuplicateGenericParameter{g->name.value}, g->location);
else
seen.insert(g->name);
if (g->defaultValue)
visit(g->defaultValue);
}
}
Scope* findInnermostScope(Location location) const
{
Scope* bestScope = module->getModuleScope().get();
bool didNarrow;
do
{
didNarrow = false;
for (auto scope : bestScope->children)
{
if (scope->location.encloses(location))
{
bestScope = scope.get();
didNarrow = true;
break;
}
}
} while (didNarrow && bestScope->children.size() > 0);
return bestScope;
}
std::optional<TypePackId> lookupPackAnnotation(AstTypePack* annotation) const
{
TypePackId* tp = module->astResolvedTypePacks.find(annotation);
if (tp != nullptr)
return {follow(*tp)};
return {};
}
void reportError(TypeErrorData data, const Location& location) void reportError(TypeErrorData data, const Location& location)
{ {
module->errors.emplace_back(location, module->name, std::move(data)); module->errors.emplace_back(location, module->name, std::move(data));

View file

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

View file

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

@ -4,7 +4,7 @@
#include "Luau/Ast.h" #include "Luau/Ast.h"
#include "Luau/Module.h" #include "Luau/Module.h"
LUAU_FASTFLAGVARIABLE(LuauExtendedSimpleRequire) LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
namespace Luau namespace Luau
{ {
@ -67,6 +67,12 @@ struct RequireTracer : AstVisitor
return true; return true;
} }
bool visit(AstTypePack* node) override
{
// allow resolving require inside `typeof` annotations
return FFlag::LuauStoreReturnTypesAsPackOnAst;
}
AstExpr* getDependent_DEPRECATED(AstExpr* node) AstExpr* getDependent_DEPRECATED(AstExpr* node)
{ {
if (AstExprLocal* expr = node->as<AstExprLocal>()) if (AstExprLocal* expr = node->as<AstExprLocal>())
@ -106,8 +112,6 @@ struct RequireTracer : AstVisitor
{ {
ModuleInfo moduleContext{currentModuleName}; ModuleInfo moduleContext{currentModuleName};
if (FFlag::LuauExtendedSimpleRequire)
{
// seed worklist with require arguments // seed worklist with require arguments
work.reserve(requireCalls.size()); work.reserve(requireCalls.size());
@ -153,50 +157,6 @@ struct RequireTracer : AstVisitor
if (info) if (info)
result.exprs[expr] = std::move(*info); result.exprs[expr] = std::move(*info);
} }
}
else
{
// seed worklist with require arguments
work_DEPRECATED.reserve(requireCalls.size());
for (AstExprCall* require : requireCalls)
work_DEPRECATED.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_DEPRECATED.size(); ++i)
if (AstExpr* dep = getDependent_DEPRECATED(work_DEPRECATED[i]))
work_DEPRECATED.push_back(dep);
// resolve all expressions to a module info
for (size_t i = work_DEPRECATED.size(); i > 0; --i)
{
AstExpr* expr = work_DEPRECATED[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 (AstExpr* dep = getDependent_DEPRECATED(expr))
{
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);
}
}
// resolve all requires according to their argument // resolve all requires according to their argument
result.requireList.reserve(requireCalls.size()); result.requireList.reserve(requireCalls.size());
@ -224,7 +184,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

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

View file

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

View file

@ -7,13 +7,11 @@
#include "Luau/Normalize.h" #include "Luau/Normalize.h"
#include "Luau/RecursionCounter.h" #include "Luau/RecursionCounter.h"
#include "Luau/Scope.h" #include "Luau/Scope.h"
#include "Luau/StringUtils.h"
#include "Luau/Substitution.h" #include "Luau/Substitution.h"
#include "Luau/ToString.h" #include "Luau/ToString.h"
#include "Luau/TxnLog.h" #include "Luau/TxnLog.h"
#include "Luau/Type.h" #include "Luau/Type.h"
#include "Luau/TypeArena.h" #include "Luau/TypeArena.h"
#include "Luau/TypeCheckLimits.h"
#include "Luau/TypeFunction.h" #include "Luau/TypeFunction.h"
#include "Luau/TypePack.h" #include "Luau/TypePack.h"
#include "Luau/TypePath.h" #include "Luau/TypePath.h"
@ -22,7 +20,8 @@
#include <algorithm> #include <algorithm>
LUAU_FASTFLAGVARIABLE(DebugLuauSubtypingCheckPathValidity) LUAU_FASTFLAGVARIABLE(DebugLuauSubtypingCheckPathValidity)
LUAU_FASTFLAGVARIABLE(LuauSubtypingFixTailPack) LUAU_FASTINTVARIABLE(LuauSubtypingReasoningLimit, 100)
LUAU_FASTFLAGVARIABLE(LuauSubtypingEnableReasoningLimit)
namespace Luau namespace Luau
{ {
@ -32,7 +31,7 @@ struct VarianceFlipper
Subtyping::Variance* variance; Subtyping::Variance* variance;
Subtyping::Variance oldValue; Subtyping::Variance oldValue;
VarianceFlipper(Subtyping::Variance* v) explicit VarianceFlipper(Subtyping::Variance* v)
: variance(v) : variance(v)
, oldValue(*v) , oldValue(*v)
{ {
@ -100,6 +99,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 +118,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;
@ -308,7 +313,7 @@ struct ApplyMappedGenerics : Substitution
bool ignoreChildren(TypeId ty) override bool ignoreChildren(TypeId ty) override
{ {
if (get<ClassType>(ty)) if (get<ExternType>(ty))
return true; return true;
return ty->persistent; return ty->persistent;
@ -416,6 +421,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 (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 +606,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 (semantic.normalizationTooComplex)
{
result = semantic;
}
else if (semantic.isSubtype)
{ {
semantic.reasoning.clear(); semantic.reasoning.clear();
result = semantic; result = semantic;
@ -608,7 +626,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 (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.
@ -719,9 +742,9 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
result = isCovariantWith(env, p, scope); result = isCovariantWith(env, p, scope);
else if (auto p = get2<MetatableType, TableType>(subTy, superTy)) else if (auto p = get2<MetatableType, TableType>(subTy, superTy))
result = isCovariantWith(env, p, scope); result = isCovariantWith(env, p, scope);
else if (auto p = get2<ClassType, ClassType>(subTy, superTy)) else if (auto p = get2<ExternType, ExternType>(subTy, superTy))
result = isCovariantWith(env, p, scope); result = isCovariantWith(env, p, scope);
else if (auto p = get2<ClassType, TableType>(subTy, superTy)) else if (auto p = get2<ExternType, TableType>(subTy, superTy))
result = isCovariantWith(env, subTy, p.first, superTy, p.second, scope); result = isCovariantWith(env, subTy, p.first, superTy, p.second, scope);
else if (auto p = get2<TableType, PrimitiveType>(subTy, superTy)) else if (auto p = get2<TableType, PrimitiveType>(subTy, superTy))
result = isCovariantWith(env, p, scope); result = isCovariantWith(env, p, scope);
@ -754,7 +777,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 +791,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 +845,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 +883,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 +1106,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 (next.normalizationTooComplex)
return SubtypingResult{false, /* normalizationTooComplex */ true};
if (next.isSubtype) if (next.isSubtype)
return SubtypingResult{true}; return SubtypingResult{true};
} }
@ -1100,7 +1128,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 (subtypings.back().normalizationTooComplex)
return SubtypingResult{false, /* normalizationTooComplex */ true};
}
return SubtypingResult::all(subtypings); return SubtypingResult::all(subtypings);
} }
@ -1110,7 +1144,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 (subtypings.back().normalizationTooComplex)
return SubtypingResult{false, /* normalizationTooComplex */ true};
}
return SubtypingResult::all(subtypings); return SubtypingResult::all(subtypings);
} }
@ -1120,7 +1160,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 (subtypings.back().normalizationTooComplex)
return SubtypingResult{false, /* normalizationTooComplex */ true};
}
return SubtypingResult::any(subtypings); return SubtypingResult::any(subtypings);
} }
@ -1288,7 +1334,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Type
} }
// the top class type is not actually a primitive type, so the negation of // the top class type is not actually a primitive type, so the negation of
// any one of them includes the top class type. // any one of them includes the top class type.
else if (auto p = get2<ClassType, PrimitiveType>(subTy, negatedTy)) else if (auto p = get2<ExternType, PrimitiveType>(subTy, negatedTy))
result = {true}; result = {true};
else if (auto p = get<PrimitiveType>(negatedTy); p && is<TableType, MetatableType>(subTy)) else if (auto p = get<PrimitiveType>(negatedTy); p && is<TableType, MetatableType>(subTy))
result = {p->type != PrimitiveType::Table}; result = {p->type != PrimitiveType::Table};
@ -1296,9 +1342,9 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Type
result = {p.second->type != PrimitiveType::Function}; result = {p.second->type != PrimitiveType::Function};
else if (auto p = get2<SingletonType, SingletonType>(subTy, negatedTy)) else if (auto p = get2<SingletonType, SingletonType>(subTy, negatedTy))
result = {*p.first != *p.second}; result = {*p.first != *p.second};
else if (auto p = get2<ClassType, ClassType>(subTy, negatedTy)) else if (auto p = get2<ExternType, ExternType>(subTy, negatedTy))
result = SubtypingResult::negate(isCovariantWith(env, p.first, p.second, scope)); result = SubtypingResult::negate(isCovariantWith(env, p.first, p.second, scope));
else if (get2<FunctionType, ClassType>(subTy, negatedTy)) else if (get2<FunctionType, ExternType>(subTy, negatedTy))
result = {true}; result = {true};
else if (is<ErrorType, FunctionType, TableType, MetatableType>(negatedTy)) else if (is<ErrorType, FunctionType, TableType, MetatableType>(negatedTy))
iceReporter->ice("attempting to negate a non-testable type"); iceReporter->ice("attempting to negate a non-testable type");
@ -1410,7 +1456,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.
@ -1423,15 +1469,15 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Meta
} }
} }
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const ClassType* subClass, const ClassType* superClass, NotNull<Scope> scope) SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const ExternType* subExternType, const ExternType* superExternType, NotNull<Scope> scope)
{ {
return {isSubclass(subClass, superClass)}; return {isSubclass(subExternType, superExternType)};
} }
SubtypingResult Subtyping::isCovariantWith( SubtypingResult Subtyping::isCovariantWith(
SubtypingEnvironment& env, SubtypingEnvironment& env,
TypeId subTy, TypeId subTy,
const ClassType* subClass, const ExternType* subExternType,
TypeId superTy, TypeId superTy,
const TableType* superTable, const TableType* superTable,
NotNull<Scope> scope NotNull<Scope> scope
@ -1443,7 +1489,7 @@ SubtypingResult Subtyping::isCovariantWith(
for (const auto& [name, prop] : superTable->props) for (const auto& [name, prop] : superTable->props)
{ {
if (auto classProp = lookupClassProp(subClass, name)) if (auto classProp = lookupExternTypeProp(subExternType, name))
{ {
result.andAlso(isCovariantWith(env, *classProp, prop, name, scope)); result.andAlso(isCovariantWith(env, *classProp, prop, name, scope));
} }
@ -1613,7 +1659,7 @@ SubtypingResult Subtyping::isCovariantWith(
SubtypingResult result = isCovariantWith(env, subNorm->tops, superNorm->tops, scope); SubtypingResult result = isCovariantWith(env, subNorm->tops, superNorm->tops, scope);
result.andAlso(isCovariantWith(env, subNorm->booleans, superNorm->booleans, scope)); result.andAlso(isCovariantWith(env, subNorm->booleans, superNorm->booleans, scope));
result.andAlso( result.andAlso(
isCovariantWith(env, subNorm->classes, superNorm->classes, scope).orElse(isCovariantWith(env, subNorm->classes, superNorm->tables, scope)) isCovariantWith(env, subNorm->externTypes, superNorm->externTypes, scope).orElse(isCovariantWith(env, subNorm->externTypes, superNorm->tables, scope))
); );
result.andAlso(isCovariantWith(env, subNorm->errors, superNorm->errors, scope)); result.andAlso(isCovariantWith(env, subNorm->errors, superNorm->errors, scope));
result.andAlso(isCovariantWith(env, subNorm->nils, superNorm->nils, scope)); result.andAlso(isCovariantWith(env, subNorm->nils, superNorm->nils, scope));
@ -1630,24 +1676,24 @@ SubtypingResult Subtyping::isCovariantWith(
SubtypingResult Subtyping::isCovariantWith( SubtypingResult Subtyping::isCovariantWith(
SubtypingEnvironment& env, SubtypingEnvironment& env,
const NormalizedClassType& subClass, const NormalizedExternType& subExternType,
const NormalizedClassType& superClass, const NormalizedExternType& superExternType,
NotNull<Scope> scope NotNull<Scope> scope
) )
{ {
for (const auto& [subClassTy, _] : subClass.classes) for (const auto& [subExternTypeTy, _] : subExternType.externTypes)
{ {
SubtypingResult result; SubtypingResult result;
for (const auto& [superClassTy, superNegations] : superClass.classes) for (const auto& [superExternTypeTy, superNegations] : superExternType.externTypes)
{ {
result.orElse(isCovariantWith(env, subClassTy, superClassTy, scope)); result.orElse(isCovariantWith(env, subExternTypeTy, superExternTypeTy, scope));
if (!result.isSubtype) if (!result.isSubtype)
continue; continue;
for (TypeId negation : superNegations) for (TypeId negation : superNegations)
{ {
result.andAlso(SubtypingResult::negate(isCovariantWith(env, subClassTy, negation, scope))); result.andAlso(SubtypingResult::negate(isCovariantWith(env, subExternTypeTy, negation, scope)));
if (result.isSubtype) if (result.isSubtype)
break; break;
} }
@ -1662,17 +1708,17 @@ SubtypingResult Subtyping::isCovariantWith(
SubtypingResult Subtyping::isCovariantWith( SubtypingResult Subtyping::isCovariantWith(
SubtypingEnvironment& env, SubtypingEnvironment& env,
const NormalizedClassType& subClass, const NormalizedExternType& subExternType,
const TypeIds& superTables, const TypeIds& superTables,
NotNull<Scope> scope NotNull<Scope> scope
) )
{ {
for (const auto& [subClassTy, _] : subClass.classes) for (const auto& [subExternTypeTy, _] : subExternType.externTypes)
{ {
SubtypingResult result; SubtypingResult result;
for (TypeId superTableTy : superTables) for (TypeId superTableTy : superTables)
result.orElse(isCovariantWith(env, subClassTy, superTableTy, scope)); result.orElse(isCovariantWith(env, subExternTypeTy, superTableTy, scope));
if (!result.isSubtype) if (!result.isSubtype)
return result; return result;
@ -1760,7 +1806,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 (results.back().normalizationTooComplex)
return SubtypingResult{false, /* normalizationTooComplex */ true};
}
} }
return SubtypingResult::all(results); return SubtypingResult::all(results);

View file

@ -13,9 +13,9 @@
#include "Luau/TypeUtils.h" #include "Luau/TypeUtils.h"
#include "Luau/Unifier2.h" #include "Luau/Unifier2.h"
LUAU_FASTFLAGVARIABLE(LuauDontInPlaceMutateTableType) LUAU_FASTFLAGVARIABLE(LuauBidirectionalInferenceCollectIndexerTypes)
LUAU_FASTFLAGVARIABLE(LuauAllowNonSharedTableTypesInLiteral) LUAU_FASTFLAGVARIABLE(LuauBidirectionalFailsafe)
LUAU_FASTFLAGVARIABLE(LuauBidirectionalInferenceUpcast) LUAU_FASTFLAGVARIABLE(LuauBidirectionalInferenceElideAssert)
namespace Luau namespace Luau
{ {
@ -138,27 +138,33 @@ 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))
{
if (FFlag::LuauBidirectionalInferenceUpcast)
{ {
auto result = subtyping->isSubtype(/*subTy=*/exprType, /*superTy=*/expectedType, unifier->scope); auto result = subtyping->isSubtype(/*subTy=*/exprType, /*superTy=*/expectedType, unifier->scope);
return result.isSubtype return result.isSubtype ? expectedType : exprType;
? expectedType
: exprType;
}
else
return exprType;
} }
expectedType = follow(expectedType); expectedType = follow(expectedType);
exprType = follow(exprType); exprType = follow(exprType);
if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes)
{
// The intent of `matchLiteralType` is to upcast values when it's safe
// 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)) if (get<AnyType>(expectedType) || get<UnknownType>(expectedType))
{ {
// "Narrowing" to unknown or any is not going to do anything useful. // "Narrowing" to unknown or any is not going to do anything useful.
return exprType; return exprType;
} }
}
if (expr->is<AstExprConstantString>()) if (expr->is<AstExprConstantString>())
{ {
@ -227,19 +233,26 @@ TypeId matchLiteralType(
} }
if (FFlag::LuauBidirectionalInferenceUpcast && expr->is<AstExprFunction>()) if (expr->is<AstExprFunction>())
{ {
// TODO: Push argument / return types into the lambda. For now, just do // TODO: Push argument / return types into the lambda. For now, just do
// the non-literal thing: check for a subtype and upcast if valid. // the non-literal thing: check for a subtype and upcast if valid.
auto result = subtyping->isSubtype(/*subTy=*/exprType, /*superTy=*/expectedType, unifier->scope); auto result = subtyping->isSubtype(/*subTy=*/exprType, /*superTy=*/expectedType, unifier->scope);
return result.isSubtype return result.isSubtype ? expectedType : exprType;
? 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);
@ -266,6 +279,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))
@ -273,23 +289,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 // If we encounter a duplcate property, we may have already
// set it to be read-only. If that's the case, the only thing // set it to be read-only. If that's the case, the only thing
// that will definitely crash is trying to access a write // that will definitely crash is trying to access a write
// only property. // only property.
LUAU_ASSERT(!prop.isWriteOnly()); 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);
@ -317,16 +330,20 @@ TypeId matchLiteralType(
toBlock toBlock
); );
if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes)
{
indexerKeyTypes.insert(arena->addType(SingletonType{StringSingleton{keyStr}}));
indexerValueTypes.insert(matchedType);
}
else
{
if (tableTy->indexer) if (tableTy->indexer)
unifier->unify(matchedType, tableTy->indexer->indexResultType); unifier->unify(matchedType, tableTy->indexer->indexResultType);
else else
tableTy->indexer = TableIndexer{expectedTableTy->indexer->indexType, matchedType}; 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
@ -349,22 +366,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, subtyping, *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, subtyping, *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, subtyping, *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;
} }
@ -381,9 +401,15 @@ 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)
{ {
if (!FFlag::LuauBidirectionalInferenceCollectIndexerTypes || !FFlag::LuauBidirectionalInferenceElideAssert)
LUAU_ASSERT(tableTy->indexer); LUAU_ASSERT(tableTy->indexer);
if (expectedTableTy->indexer) if (expectedTableTy->indexer)
@ -405,11 +431,19 @@ TypeId matchLiteralType(
toBlock toBlock
); );
if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes)
{
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 the index result type is the prop type, we can replace it with the matched type here.
if (tableTy->indexer->indexResultType == *propTy) if (tableTy->indexer->indexResultType == *propTy)
tableTy->indexer->indexResultType = matchedType; tableTy->indexer->indexResultType = matchedType;
} }
} }
}
else if (item.kind == AstExprTable::Item::General) else if (item.kind == AstExprTable::Item::General)
{ {
@ -429,20 +463,23 @@ 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; const AstArray<char>& s = key->value;
std::string keyStr{s.data, s.data + s.size}; std::string keyStr{s.data, s.data + s.size};
tableTy->props.erase(keyStr); 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
// specified by the AST fragment. // specified by the AST fragment.
@ -493,11 +530,41 @@ 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 (FFlag::LuauBidirectionalInferenceCollectIndexerTypes && 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) if (expectedTableTy->indexer && !tableTy->indexer)
{ {
tableTy->indexer = expectedTableTy->indexer; tableTy->indexer = expectedTableTy->indexer;
} }
} }
}
return exprType; return exprType;
} }

View file

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

View file

@ -19,8 +19,11 @@
#include <stdexcept> #include <stdexcept>
#include <string> #include <string>
LUAU_FASTFLAGVARIABLE(LuauEnableDenseTableAlias)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAGVARIABLE(LuauSyntheticErrors) LUAU_FASTFLAGVARIABLE(LuauSyntheticErrors)
LUAU_FASTFLAGVARIABLE(LuauStringPartLengthLimit)
/* /*
* Enables increasing levels of verbosity for Luau type names when stringifying. * Enables increasing levels of verbosity for Luau type names when stringifying.
@ -120,7 +123,7 @@ struct FindCyclicTypes final : TypeVisitor
return true; return true;
} }
bool visit(TypeId ty, const ClassType&) override bool visit(TypeId ty, const ExternType&) override
{ {
return false; return false;
} }
@ -301,6 +304,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 +507,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 +521,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 +539,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 +571,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("-");
@ -686,7 +722,13 @@ struct TypeStringifier
if (ttv.boundTo) if (ttv.boundTo)
return stringify(*ttv.boundTo); return stringify(*ttv.boundTo);
if (!state.exhaustive) bool showName = !state.exhaustive;
if (FFlag::LuauEnableDenseTableAlias)
{
// if hide table alias expansions are enabled and there is a name found for the table, use it
showName = !state.exhaustive || state.opts.hideTableAliasExpansions;
}
if (showName)
{ {
if (ttv.name) if (ttv.name)
{ {
@ -709,6 +751,10 @@ struct TypeStringifier
stringify(ttv.instantiatedTypeParams, ttv.instantiatedTypePackParams); stringify(ttv.instantiatedTypeParams, ttv.instantiatedTypePackParams);
return; return;
} }
}
if (!state.exhaustive)
{
if (ttv.syntheticName) if (ttv.syntheticName)
{ {
state.result.invalid = true; state.result.invalid = true;
@ -847,9 +893,9 @@ struct TypeStringifier
state.emit(" }"); state.emit(" }");
} }
void operator()(TypeId, const ClassType& ctv) void operator()(TypeId, const ExternType& etv)
{ {
state.emit(ctv.name); state.emit(etv.name);
} }
void operator()(TypeId, const AnyType&) void operator()(TypeId, const AnyType&)
@ -877,6 +923,9 @@ struct TypeStringifier
bool hasNonNilDisjunct = false; bool hasNonNilDisjunct = false;
std::vector<std::string> results = {}; std::vector<std::string> results = {};
size_t resultsLength = 0;
bool lengthLimitHit = false;
for (auto el : &uv) for (auto el : &uv)
{ {
el = follow(el); el = follow(el);
@ -903,14 +952,34 @@ struct TypeStringifier
if (needParens) if (needParens)
state.emit(")"); state.emit(")");
if (FFlag::LuauStringPartLengthLimit)
resultsLength += state.result.name.length();
results.push_back(std::move(state.result.name)); results.push_back(std::move(state.result.name));
state.result.name = std::move(saved); state.result.name = std::move(saved);
if (FFlag::LuauStringPartLengthLimit)
{
lengthLimitHit = state.opts.maxTypeLength > 0 && resultsLength > state.opts.maxTypeLength;
if (lengthLimitHit)
break;
}
} }
state.unsee(&uv); state.unsee(&uv);
if (FFlag::LuauStringPartLengthLimit)
{
if (!lengthLimitHit && !FFlag::DebugLuauToStringNoLexicalSort)
std::sort(results.begin(), results.end());
}
else
{
if (!FFlag::DebugLuauToStringNoLexicalSort) if (!FFlag::DebugLuauToStringNoLexicalSort)
std::sort(results.begin(), results.end()); std::sort(results.begin(), results.end());
}
if (optional && results.size() > 1) if (optional && results.size() > 1)
state.emit("("); state.emit("(");
@ -954,6 +1023,9 @@ struct TypeStringifier
} }
std::vector<std::string> results = {}; std::vector<std::string> results = {};
size_t resultsLength = 0;
bool lengthLimitHit = false;
for (auto el : uv.parts) for (auto el : uv.parts)
{ {
el = follow(el); el = follow(el);
@ -970,14 +1042,34 @@ struct TypeStringifier
if (needParens) if (needParens)
state.emit(")"); state.emit(")");
if (FFlag::LuauStringPartLengthLimit)
resultsLength += state.result.name.length();
results.push_back(std::move(state.result.name)); results.push_back(std::move(state.result.name));
state.result.name = std::move(saved); state.result.name = std::move(saved);
if (FFlag::LuauStringPartLengthLimit)
{
lengthLimitHit = state.opts.maxTypeLength > 0 && resultsLength > state.opts.maxTypeLength;
if (lengthLimitHit)
break;
}
} }
state.unsee(&uv); state.unsee(&uv);
if (FFlag::LuauStringPartLengthLimit)
{
if (!lengthLimitHit && !FFlag::DebugLuauToStringNoLexicalSort)
std::sort(results.begin(), results.end());
}
else
{
if (!FFlag::DebugLuauToStringNoLexicalSort) if (!FFlag::DebugLuauToStringNoLexicalSort)
std::sort(results.begin(), results.end()); std::sort(results.begin(), results.end());
}
bool first = true; bool first = true;
bool shouldPlaceOnNewlines = results.size() > state.opts.compositeTypesSingleLineLimit || isOverloadedFunction(ty); bool shouldPlaceOnNewlines = results.size() > state.opts.compositeTypesSingleLineLimit || isOverloadedFunction(ty);
@ -1222,6 +1314,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 +1336,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

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

View file

@ -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(LuauAstTypeGroup3)
LUAU_FASTFLAG(LuauFixDoBlockEndLocation) LUAU_FASTFLAG(LuauParseOptionalAsNode2)
LUAU_FASTFLAG(LuauFixFunctionWithAttributesStartLocation)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
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)
@ -293,7 +331,7 @@ struct Printer_DEPRECATED
} }
} }
void visualizeTypePackAnnotation(const AstTypePack& annotation, bool forVarArg) void visualizeTypePackAnnotation(const AstTypePack& annotation, bool forVarArg, bool unconditionallyParenthesize = true)
{ {
advance(annotation.location.begin); advance(annotation.location.begin);
if (const AstTypePackVariadic* variadicTp = annotation.as<AstTypePackVariadic>()) if (const AstTypePackVariadic* variadicTp = annotation.as<AstTypePackVariadic>())
@ -311,7 +349,7 @@ struct Printer_DEPRECATED
else if (const AstTypePackExplicit* explicitTp = annotation.as<AstTypePackExplicit>()) else if (const AstTypePackExplicit* explicitTp = annotation.as<AstTypePackExplicit>())
{ {
LUAU_ASSERT(!forVarArg); LUAU_ASSERT(!forVarArg);
visualizeTypeList(explicitTp->typeList, true); visualizeTypeList(explicitTp->typeList, unconditionallyParenthesize);
} }
else else
{ {
@ -667,8 +705,6 @@ struct Printer_DEPRECATED
writer.keyword("do"); writer.keyword("do");
for (const auto& s : block->body) for (const auto& s : block->body)
visualize(*s); visualize(*s);
if (!FFlag::LuauFixDoBlockEndLocation)
writer.advance(block->location.end);
writeEnd(program.location); writeEnd(program.location);
} }
else if (const auto& a = program.as<AstStatIf>()) else if (const auto& a = program.as<AstStatIf>())
@ -1027,12 +1063,15 @@ struct Printer_DEPRECATED
writer.symbol(")"); writer.symbol(")");
if (writeTypes && func.returnAnnotation) if (writeTypes && (FFlag::LuauStoreReturnTypesAsPackOnAst ? func.returnAnnotation != nullptr : func.returnAnnotation_DEPRECATED.has_value()))
{ {
writer.symbol(":"); writer.symbol(":");
writer.space(); writer.space();
visualizeTypeList(*func.returnAnnotation, false); if (FFlag::LuauStoreReturnTypesAsPackOnAst)
visualizeTypePackAnnotation(*func.returnAnnotation, false, false);
else
visualizeTypeList(*func.returnAnnotation_DEPRECATED, false);
} }
visualizeBlock(*func.body); visualizeBlock(*func.body);
@ -1136,7 +1175,10 @@ struct Printer_DEPRECATED
} }
writer.symbol("->"); writer.symbol("->");
visualizeTypeList(a->returnTypes, true); if (FFlag::LuauStoreReturnTypesAsPackOnAst)
visualizeTypePackAnnotation(*a->returnTypes, false);
else
visualizeTypeList(a->returnTypes_DEPRECATED, true);
} }
else if (const auto& a = typeAnnotation.as<AstTypeTable>()) else if (const auto& a = typeAnnotation.as<AstTypeTable>())
{ {
@ -1191,9 +1233,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];
if (FFlag::LuauParseOptionalAsNode2)
{
auto lta = l->as<AstTypeReference>();
if (lta && lta->name == "nil" && !r->is<AstTypeOptional>())
std::swap(l, r);
}
else
{
auto lta = l->as<AstTypeReference>(); auto lta = l->as<AstTypeReference>();
if (lta && lta->name == "nil") if (lta && lta->name == "nil")
std::swap(l, r); 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 +1267,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 +1372,7 @@ struct Printer
} }
} }
void visualizeTypePackAnnotation(const AstTypePack& annotation, bool forVarArg) void visualizeTypePackAnnotation(AstTypePack& annotation, bool forVarArg, bool unconditionallyParenthesize = true)
{ {
advance(annotation.location.begin); advance(annotation.location.begin);
if (const AstTypePackVariadic* variadicTp = annotation.as<AstTypePackVariadic>()) if (const AstTypePackVariadic* variadicTp = annotation.as<AstTypePackVariadic>())
@ -1322,15 +1382,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, FFlag::LuauStoreReturnTypesAsPackOnAst ? cstNode->hasParentheses : true, cstNode->openParenthesesPosition, cstNode->closeParenthesesPosition, cstNode->commaPositions
);
else
visualizeTypeList(explicitTp->typeList, unconditionallyParenthesize);
} }
else else
{ {
@ -1338,19 +1405,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::LuauAstTypeGroup3 ? 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)
@ -1363,33 +1448,50 @@ struct Printer
} }
if (FFlag::LuauAstTypeGroup3 ? 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 +1502,14 @@ struct Printer
void visualize(AstExpr& expr) void visualize(AstExpr& expr)
{ {
if (!expr.is<AstExprFunction>() || FFlag::LuauFixFunctionWithAttributesStartLocation)
advance(expr.location.begin); 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 +1637,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,8 +1889,17 @@ 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)
{ {
if ((!program.is<AstStatLocalFunction>() && !program.is<AstStatFunction>()) || FFlag::LuauFixFunctionWithAttributesStartLocation)
advance(program.location.begin); advance(program.location.begin);
if (const auto& block = program.as<AstStatBlock>()) if (const auto& block = program.as<AstStatBlock>())
@ -1817,8 +1940,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 +2137,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 +2267,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 +2310,21 @@ 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);
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 +2339,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,14 +2381,27 @@ struct Printer
} }
} }
if (func.argLocation)
advanceBefore(func.argLocation->end, 1);
writer.symbol(")"); writer.symbol(")");
if (writeTypes && func.returnAnnotation) if (writeTypes && FFlag::LuauStoreReturnTypesAsPackOnAst ? func.returnAnnotation != nullptr : func.returnAnnotation_DEPRECATED.has_value())
{ {
if (cstNode)
advance(cstNode->returnSpecifierPosition);
writer.symbol(":"); writer.symbol(":");
writer.space();
visualizeTypeList(*func.returnAnnotation, false); if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
if (!cstNode)
writer.space();
visualizeTypePackAnnotation(*func.returnAnnotation, false, false);
}
else
{
writer.space();
visualizeTypeList(*func.returnAnnotation_DEPRECATED, false);
}
} }
visualizeBlock(*func.body); visualizeBlock(*func.body);
@ -2301,6 +2483,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 +2539,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,17 +2560,34 @@ 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); if (FFlag::LuauStoreReturnTypesAsPackOnAst)
visualizeTypePackAnnotation(*a->returnTypes, false);
else
visualizeTypeList(a->returnTypes_DEPRECATED, true);
} }
else if (const auto& a = typeAnnotation.as<AstTypeTable>()) else if (const auto& a = typeAnnotation.as<AstTypeTable>())
{ {
@ -2440,6 +2660,7 @@ struct Printer
{ {
advance(item.indexerOpenPosition); advance(item.indexerOpenPosition);
writer.symbol("["); writer.symbol("[");
advance(item.stringPosition);
writer.sourceString( writer.sourceString(
std::string_view(item.stringInfo->sourceString.data, item.stringInfo->sourceString.size), std::string_view(item.stringInfo->sourceString.data, item.stringInfo->sourceString.size),
item.stringInfo->quoteStyle, item.stringInfo->quoteStyle,
@ -2527,14 +2748,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];
if (FFlag::LuauParseOptionalAsNode2)
{
auto lta = l->as<AstTypeReference>();
if (lta && lta->name == "nil" && !r->is<AstTypeOptional>())
std::swap(l, r);
}
else
{
auto lta = l->as<AstTypeReference>(); auto lta = l->as<AstTypeReference>();
if (lta && lta->name == "nil") if (lta && lta->name == "nil")
std::swap(l, r); 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 +2787,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)
{ {
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.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 +2832,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)
{ {
if (cstNode)
advance(cstNode->separatorPositions.data[i - 1]);
else
writer.maybeSpace(a->types.data[i]->location.begin, 2); 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 +2867,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 +2901,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 +2937,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 +2951,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

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

View file

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

View file

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

View file

@ -26,10 +26,15 @@
#include "Luau/VisitType.h" #include "Luau/VisitType.h"
#include <algorithm> #include <algorithm>
#include <sstream>
LUAU_FASTFLAG(DebugLuauMagicTypes) LUAU_FASTFLAG(DebugLuauMagicTypes)
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds) LUAU_FASTFLAGVARIABLE(LuauImproveTypePathsInErrors)
LUAU_FASTFLAG(LuauUserTypeFunTypecheck)
LUAU_FASTFLAGVARIABLE(LuauTypeCheckerAcceptNumberConcats)
LUAU_FASTFLAGVARIABLE(LuauTypeCheckerStricterIndexCheck)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
namespace Luau namespace Luau
{ {
@ -657,7 +662,7 @@ void TypeChecker2::visit(AstStat* stat)
return visit(s); return visit(s);
else if (auto s = stat->as<AstStatDeclareGlobal>()) else if (auto s = stat->as<AstStatDeclareGlobal>())
return visit(s); return visit(s);
else if (auto s = stat->as<AstStatDeclareClass>()) else if (auto s = stat->as<AstStatDeclareExternType>())
return visit(s); return visit(s);
else if (auto s = stat->as<AstStatError>()) else if (auto s = stat->as<AstStatError>())
return visit(s); return visit(s);
@ -1201,7 +1206,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)
@ -1216,7 +1222,10 @@ void TypeChecker2::visit(AstStatDeclareFunction* stat)
{ {
visitGenerics(stat->generics, stat->genericPacks); visitGenerics(stat->generics, stat->genericPacks);
visit(stat->params); visit(stat->params);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
visit(stat->retTypes); visit(stat->retTypes);
else
visit(stat->retTypes_DEPRECATED);
} }
void TypeChecker2::visit(AstStatDeclareGlobal* stat) void TypeChecker2::visit(AstStatDeclareGlobal* stat)
@ -1224,9 +1233,9 @@ void TypeChecker2::visit(AstStatDeclareGlobal* stat)
visit(stat->type); visit(stat->type);
} }
void TypeChecker2::visit(AstStatDeclareClass* stat) void TypeChecker2::visit(AstStatDeclareExternType* stat)
{ {
for (const AstDeclaredClassProp& prop : stat->props) for (const AstDeclaredExternTypeProperty& prop : stat->props)
visit(prop.ty); visit(prop.ty);
} }
@ -1670,12 +1679,12 @@ void TypeChecker2::visit(AstExprIndexExpr* indexExpr, ValueContext context)
{ {
return indexExprMetatableHelper(indexExpr, mt, exprType, indexType); return indexExprMetatableHelper(indexExpr, mt, exprType, indexType);
} }
else if (auto cls = get<ClassType>(exprType)) else if (auto cls = get<ExternType>(exprType))
{ {
if (cls->indexer) if (cls->indexer)
testIsSubtype(indexType, cls->indexer->indexType, indexExpr->index->location); testIsSubtype(indexType, cls->indexer->indexType, indexExpr->index->location);
else else
reportError(DynamicPropertyLookupOnClassesUnsafe{exprType}, indexExpr->location); reportError(DynamicPropertyLookupOnExternTypesUnsafe{exprType}, indexExpr->location);
} }
else if (get<UnionType>(exprType) && isOptional(exprType)) else if (get<UnionType>(exprType) && isOptional(exprType))
{ {
@ -1816,8 +1825,16 @@ void TypeChecker2::visit(AstExprFunction* fn)
visit(fn->body); visit(fn->body);
// we need to typecheck the return annotation itself, if it exists. // we need to typecheck the return annotation itself, if it exists.
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
if (fn->returnAnnotation) if (fn->returnAnnotation)
visit(*fn->returnAnnotation); visit(fn->returnAnnotation);
}
else
{
if (fn->returnAnnotation_DEPRECATED)
visit(*fn->returnAnnotation_DEPRECATED);
}
// If the function type has a function annotation, we need to see if we can suggest an annotation // If the function type has a function annotation, we need to see if we can suggest an annotation
@ -2031,7 +2048,7 @@ TypeId TypeChecker2::visit(AstExprBinary* expr, AstNode* overrideKey)
// If we're working with things that are not tables, the metatable comparisons above are a little excessive // If we're working with things that are not tables, the metatable comparisons above are a little excessive
// It's ok for one type to have a meta table and the other to not. In that case, we should fall back on // It's ok for one type to have a meta table and the other to not. In that case, we should fall back on
// checking if the intersection of the types is inhabited. If `typesHaveIntersection` failed due to limits, // checking if the intersection of the types is inhabited. If `typesHaveIntersection` failed due to limits,
// TODO: Maybe add more checks here (e.g. for functions, classes, etc) // TODO: Maybe add more checks here (e.g. for functions, extern types, etc)
if (!(get<TableType>(leftType) || get<TableType>(rightType))) if (!(get<TableType>(leftType) || get<TableType>(rightType)))
if (!leftMt.has_value() || !rightMt.has_value()) if (!leftMt.has_value() || !rightMt.has_value())
matches = matches || typesHaveIntersection != NormalizationResult::False; matches = matches || typesHaveIntersection != NormalizationResult::False;
@ -2096,10 +2113,7 @@ TypeId TypeChecker2::visit(AstExprBinary* expr, AstNode* overrideKey)
} }
else else
{ {
expectedRets = module->internalTypes.addTypePack( expectedRets = module->internalTypes.addTypePack({module->internalTypes.freshType(builtinTypes, scope, TypeLevel{})});
{FFlag::LuauFreeTypesMustHaveBounds ? module->internalTypes.freshType(builtinTypes, scope, TypeLevel{})
: module->internalTypes.freshType_DEPRECATED(scope, TypeLevel{})}
);
} }
TypeId expectedTy = module->internalTypes.addType(FunctionType(expectedArgs, expectedRets)); TypeId expectedTy = module->internalTypes.addType(FunctionType(expectedArgs, expectedRets));
@ -2225,10 +2239,21 @@ TypeId TypeChecker2::visit(AstExprBinary* expr, AstNode* overrideKey)
return builtinTypes->numberType; return builtinTypes->numberType;
case AstExprBinary::Op::Concat: case AstExprBinary::Op::Concat:
{
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(leftType, builtinTypes->stringType, expr->left->location);
testIsSubtype(rightType, builtinTypes->stringType, expr->right->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:
@ -2351,8 +2376,7 @@ TypeId TypeChecker2::flattenPack(TypePackId pack)
return *fst; return *fst;
else if (auto ftp = get<FreeTypePack>(pack)) else if (auto ftp = get<FreeTypePack>(pack))
{ {
TypeId result = FFlag::LuauFreeTypesMustHaveBounds ? module->internalTypes.freshType(builtinTypes, ftp->scope) TypeId result = module->internalTypes.freshType(builtinTypes, ftp->scope);
: module->internalTypes.addType(FreeType{ftp->scope});
TypePackId freeTail = module->internalTypes.addTypePack(FreeTypePack{ftp->scope}); TypePackId freeTail = module->internalTypes.addTypePack(FreeTypePack{ftp->scope});
TypePack* resultPack = emplaceTypePack<TypePack>(asMutable(pack)); TypePack* resultPack = emplaceTypePack<TypePack>(asMutable(pack));
@ -2597,7 +2621,10 @@ void TypeChecker2::visit(AstTypeFunction* ty)
{ {
visitGenerics(ty->generics, ty->genericPacks); visitGenerics(ty->generics, ty->genericPacks);
visit(ty->argTypes); visit(ty->argTypes);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
visit(ty->returnTypes); visit(ty->returnTypes);
else
visit(ty->returnTypes_DEPRECATED);
} }
void TypeChecker2::visit(AstTypeTypeof* ty) void TypeChecker2::visit(AstTypeTypeof* ty)
@ -2701,6 +2728,46 @@ 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);
if (FFlag::LuauImproveTypePathsInErrors)
{
std::string relation = "a subtype of";
if (reasoning.variance == SubtypingVariance::Invariant)
relation = "exactly";
else if (reasoning.variance == SubtypingVariance::Contravariant)
relation = "a supertype of";
std::string subLeafAsString = toString(subLeaf);
// if the string is empty, it must be an empty type pack
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
{
std::string relation = "a subtype of"; std::string relation = "a subtype of";
if (reasoning.variance == SubtypingVariance::Invariant) if (reasoning.variance == SubtypingVariance::Invariant)
relation = "exactly"; relation = "exactly";
@ -2715,6 +2782,7 @@ Reasonings TypeChecker2::explainReasonings_(TID subTy, TID superTy, Location loc
relation + " " + toString(superTy) + toString(reasoning.superPath, /* prefixDot */ true) + " (" + toString(superLeaf) + ")"; relation + " " + toString(superTy) + toString(reasoning.superPath, /* prefixDot */ true) + " (" + toString(superLeaf) + ")";
reasons.push_back(reason); 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)
@ -2880,7 +2948,7 @@ PropertyTypes TypeChecker2::lookupProp(
if (normValid) if (normValid)
{ {
for (const auto& [ty, _negations] : norm->classes.classes) for (const auto& [ty, _negations] : norm->externTypes.externTypes)
{ {
fetch(ty); fetch(ty);
@ -2975,10 +3043,10 @@ void TypeChecker2::checkIndexTypeFromType(
if (propTypes.foundOneProp()) if (propTypes.foundOneProp())
reportError(MissingUnionProperty{tableTy, propTypes.missingProp, prop}, location); reportError(MissingUnionProperty{tableTy, propTypes.missingProp, prop}, location);
// For class LValues, we don't want to report an extension error, // For class LValues, we don't want to report an extension error,
// because classes come into being with full knowledge of their // because extern types come into being with full knowledge of their
// shape. We instead want to report the unknown property error of // shape. We instead want to report the unknown property error of
// the `else` branch. // the `else` branch.
else if (context == ValueContext::LValue && !get<ClassType>(tableTy)) else if (context == ValueContext::LValue && !get<ExternType>(tableTy))
{ {
const auto lvPropTypes = lookupProp(norm.get(), prop, ValueContext::RValue, location, astIndexExprType, dummy); const auto lvPropTypes = lookupProp(norm.get(), prop, ValueContext::RValue, location, astIndexExprType, dummy);
if (lvPropTypes.foundOneProp() && lvPropTypes.noneMissingProp()) if (lvPropTypes.foundOneProp() && lvPropTypes.noneMissingProp())
@ -2988,7 +3056,7 @@ void TypeChecker2::checkIndexTypeFromType(
else else
reportError(CannotExtendTable{tableTy, CannotExtendTable::Property, prop}, location); reportError(CannotExtendTable{tableTy, CannotExtendTable::Property, prop}, location);
} }
else if (context == ValueContext::RValue && !get<ClassType>(tableTy)) else if (context == ValueContext::RValue && !get<ExternType>(tableTy))
{ {
const auto rvPropTypes = lookupProp(norm.get(), prop, ValueContext::LValue, location, astIndexExprType, dummy); const auto rvPropTypes = lookupProp(norm.get(), prop, ValueContext::LValue, location, astIndexExprType, dummy);
if (rvPropTypes.foundOneProp() && rvPropTypes.noneMissingProp()) if (rvPropTypes.foundOneProp() && rvPropTypes.noneMissingProp())
@ -3041,19 +3109,25 @@ PropertyType TypeChecker2::hasIndexTypeFromType(
return {NormalizationResult::True, {tt->indexer->indexResultType}}; return {NormalizationResult::True, {tt->indexer->indexResultType}};
} }
if (FFlag::LuauTypeCheckerStricterIndexCheck)
{
return {NormalizationResult::False, {builtinTypes->unknownType}};
}
else
{
// if we are in a conditional context, we treat the property as present and `unknown` because // if we are in a conditional context, we treat the property as present and `unknown` because
// we may be _refining_ `tableTy` to include that property. we will want to revisit this a bit // we may be _refining_ `tableTy` to include that property. we will want to revisit this a bit
// in the future once luau has support for exact tables since this only applies when inexact. // in the future once luau has support for exact tables since this only applies when inexact.
return {inConditional(typeContext) ? NormalizationResult::True : NormalizationResult::False, {builtinTypes->unknownType}}; return {inConditional(typeContext) ? NormalizationResult::True : NormalizationResult::False, {builtinTypes->unknownType}};
} }
else if (const ClassType* cls = get<ClassType>(ty)) }
else if (const ExternType* cls = get<ExternType>(ty))
{ {
// If the property doesn't exist on the class, we consult the indexer // If the property doesn't exist on the class, we consult the indexer
// We need to check if the type of the index expression foo (x[foo]) // We need to check if the type of the index expression foo (x[foo])
// is compatible with the indexer's indexType // is compatible with the indexer's indexType
// Construct the intersection and test inhabitedness! // Construct the intersection and test inhabitedness!
if (auto property = lookupClassProp(cls, prop)) if (auto property = lookupExternTypeProp(cls, prop))
return {NormalizationResult::True, context == ValueContext::LValue ? property->writeTy : property->readTy}; return {NormalizationResult::True, context == ValueContext::LValue ? property->writeTy : property->readTy};
if (cls->indexer) if (cls->indexer)
{ {
@ -3126,17 +3200,17 @@ void TypeChecker2::diagnoseMissingTableKey(UnknownProperty* utk, TypeErrorData&
if (auto ttv = getTableType(utk->table)) if (auto ttv = getTableType(utk->table))
accumulate(ttv->props); accumulate(ttv->props);
else if (auto ctv = get<ClassType>(follow(utk->table))) else if (auto etv = get<ExternType>(follow(utk->table)))
{ {
while (ctv) while (etv)
{ {
accumulate(ctv->props); accumulate(etv->props);
if (!ctv->parent) if (!etv->parent)
break; break;
ctv = get<ClassType>(*ctv->parent); etv = get<ExternType>(*etv->parent);
LUAU_ASSERT(ctv); LUAU_ASSERT(etv);
} }
} }

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

@ -11,10 +11,9 @@
#include <algorithm> #include <algorithm>
LUAU_FASTFLAG(LuauSolverV2); LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTFLAG(LuauAutocompleteRefactorsForIncrementalAutocomplete); LUAU_FASTFLAG(LuauNonReentrantGeneralization2)
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope);
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
LUAU_FASTFLAG(LuauDisableNewSolverAssertsInMixedMode) LUAU_FASTFLAG(LuauDisableNewSolverAssertsInMixedMode)
namespace Luau namespace Luau
{ {
@ -304,7 +303,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::LuauNonReentrantGeneralization2)
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,13 +322,12 @@ 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)
trackInteriorFreeType(ftp->scope, t); trackInteriorFreeType(ftp->scope, t);
} }
else else
t = FFlag::LuauFreeTypesMustHaveBounds ? arena.freshType(builtinTypes, ftp->scope) : arena.freshType_DEPRECATED(ftp->scope); t = arena.freshType(builtinTypes, ftp->scope);
} }
newPack.head.push_back(t); newPack.head.push_back(t);
@ -432,7 +434,6 @@ TypeId stripNil(NotNull<BuiltinTypes> builtinTypes, TypeArena& arena, TypeId ty)
ErrorSuppression shouldSuppressErrors(NotNull<Normalizer> normalizer, TypeId ty) ErrorSuppression shouldSuppressErrors(NotNull<Normalizer> normalizer, TypeId ty)
{ {
LUAU_ASSERT(FFlag::LuauSolverV2 || FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete);
std::shared_ptr<const NormalizedType> normType = normalizer->normalize(ty); std::shared_ptr<const NormalizedType> normType = normalizer->normalize(ty);
if (!normType) if (!normType)
@ -550,10 +551,8 @@ std::vector<TypeId> findBlockedArgTypesIn(AstExprCall* expr, NotNull<DenseHashMa
void trackInteriorFreeType(Scope* scope, TypeId ty) void trackInteriorFreeType(Scope* scope, TypeId ty)
{ {
if (FFlag::LuauDisableNewSolverAssertsInMixedMode) if (!FFlag::LuauDisableNewSolverAssertsInMixedMode)
LUAU_ASSERT(FFlag::LuauTrackInteriorFreeTypesOnScope); LUAU_ASSERT(FFlag::LuauSolverV2);
else
LUAU_ASSERT(FFlag::LuauSolverV2 && FFlag::LuauTrackInteriorFreeTypesOnScope);
for (; scope; scope = scope->parent.get()) for (; scope; scope = scope->parent.get())
{ {
if (scope->interiorFreeTypes) if (scope->interiorFreeTypes)
@ -568,4 +567,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::LuauNonReentrantGeneralization2)
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

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

View file

@ -18,8 +18,7 @@
#include <optional> #include <optional>
LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAGVARIABLE(LuauUnifyMetatableWithAny) LUAU_FASTFLAG(LuauNonReentrantGeneralization2)
LUAU_FASTFLAG(LuauExtraFollows)
namespace Luau namespace Luau
{ {
@ -237,9 +236,9 @@ bool Unifier2::unify(TypeId subTy, TypeId superTy)
auto superMetatable = get<MetatableType>(superTy); auto superMetatable = get<MetatableType>(superTy);
if (subMetatable && superMetatable) if (subMetatable && superMetatable)
return unify(subMetatable, superMetatable); return unify(subMetatable, superMetatable);
else if (FFlag::LuauUnifyMetatableWithAny && subMetatable && superAny) else if (subMetatable && superAny)
return unify(subMetatable, superAny); return unify(subMetatable, superAny);
else if (FFlag::LuauUnifyMetatableWithAny && subAny && superMetatable) else if (subAny && superMetatable)
return unify(subAny, superMetatable); return unify(subAny, superMetatable);
else if (subMetatable) // if we only have one metatable, unify with the inner table else if (subMetatable) // if we only have one metatable, unify with the inner table
return unify(subMetatable->table, superTy); return unify(subMetatable->table, superTy);
@ -283,7 +282,7 @@ bool Unifier2::unifyFreeWithType(TypeId subTy, TypeId superTy)
if (superArgTail) if (superArgTail)
return doDefault(); return doDefault();
const IntersectionType* upperBoundIntersection = get<IntersectionType>(FFlag::LuauExtraFollows ? upperBound : subFree->upperBound); const IntersectionType* upperBoundIntersection = get<IntersectionType>(upperBound);
if (!upperBoundIntersection) if (!upperBoundIntersection)
return doDefault(); return doDefault();
@ -320,12 +319,25 @@ bool Unifier2::unify(TypeId subTy, const FunctionType* superFn)
if (shouldInstantiate) if (shouldInstantiate)
{ {
for (auto generic : subFn->generics) for (TypeId generic : subFn->generics)
genericSubstitutions[generic] = freshType(arena, builtinTypes, scope); {
const GenericType* gen = get<GenericType>(follow(generic));
if (gen)
genericSubstitutions[generic] = freshType(scope, gen->polarity);
}
for (auto genericPack : subFn->genericPacks) for (TypePackId genericPack : subFn->genericPacks)
{
if (FFlag::LuauNonReentrantGeneralization2)
{
const GenericTypePack* gen = get<GenericTypePack>(follow(genericPack));
if (gen)
genericPackSubstitutions[genericPack] = freshTypePack(scope, gen->polarity);
}
else
genericPackSubstitutions[genericPack] = arena->freshTypePack(scope); genericPackSubstitutions[genericPack] = arena->freshTypePack(scope);
} }
}
bool argResult = unify(superFn->argTypes, subFn->argTypes); bool argResult = unify(superFn->argTypes, subFn->argTypes);
bool retResult = unify(subFn->retTypes, superFn->retTypes); bool retResult = unify(subFn->retTypes, superFn->retTypes);
@ -433,9 +445,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);
@ -640,208 +649,6 @@ bool Unifier2::unify(TypePackId subTp, TypePackId superTp)
return true; return true;
} }
struct FreeTypeSearcher : TypeVisitor
{
NotNull<Scope> scope;
explicit FreeTypeSearcher(NotNull<Scope> scope)
: TypeVisitor(/*skipBoundTypes*/ true)
, scope(scope)
{
}
enum Polarity
{
Positive,
Negative,
Both,
};
Polarity polarity = Positive;
void flip()
{
switch (polarity)
{
case Positive:
polarity = Negative;
break;
case Negative:
polarity = Positive;
break;
case Both:
break;
}
}
DenseHashSet<const void*> seenPositive{nullptr};
DenseHashSet<const void*> seenNegative{nullptr};
bool seenWithPolarity(const void* ty)
{
switch (polarity)
{
case Positive:
{
if (seenPositive.contains(ty))
return true;
seenPositive.insert(ty);
return false;
}
case Negative:
{
if (seenNegative.contains(ty))
return true;
seenNegative.insert(ty);
return false;
}
case Both:
{
if (seenPositive.contains(ty) && seenNegative.contains(ty))
return true;
seenPositive.insert(ty);
seenNegative.insert(ty);
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> positiveTypes{0};
bool visit(TypeId ty) override
{
if (seenWithPolarity(ty))
return false;
LUAU_ASSERT(ty);
return true;
}
bool visit(TypeId ty, const FreeType& ft) override
{
if (seenWithPolarity(ty))
return false;
if (!subsumes(scope, ft.scope))
return true;
switch (polarity)
{
case Positive:
positiveTypes[ty]++;
break;
case Negative:
negativeTypes[ty]++;
break;
case Both:
positiveTypes[ty]++;
negativeTypes[ty]++;
break;
}
return true;
}
bool visit(TypeId ty, const TableType& tt) override
{
if (seenWithPolarity(ty))
return false;
if ((tt.state == TableState::Free || tt.state == TableState::Unsealed) && subsumes(scope, tt.scope))
{
switch (polarity)
{
case Positive:
positiveTypes[ty]++;
break;
case Negative:
negativeTypes[ty]++;
break;
case Both:
positiveTypes[ty]++;
negativeTypes[ty]++;
break;
}
}
for (const auto& [_name, prop] : tt.props)
{
if (prop.isReadOnly())
traverse(*prop.readTy);
else
{
LUAU_ASSERT(prop.isShared());
Polarity p = polarity;
polarity = Both;
traverse(prop.type());
polarity = p;
}
}
if (tt.indexer)
{
traverse(tt.indexer->indexType);
traverse(tt.indexer->indexResultType);
}
return false;
}
bool visit(TypeId ty, const FunctionType& ft) override
{
if (seenWithPolarity(ty))
return false;
flip();
traverse(ft.argTypes);
flip();
traverse(ft.retTypes);
return false;
}
bool visit(TypeId, const ClassType&) override
{
return false;
}
bool visit(TypePackId tp, const FreeTypePack& ftp) override
{
if (seenWithPolarity(tp))
return false;
if (!subsumes(scope, ftp.scope))
return true;
switch (polarity)
{
case Positive:
positiveTypes[tp]++;
break;
case Negative:
negativeTypes[tp]++;
break;
case Both:
positiveTypes[tp]++;
negativeTypes[tp]++;
break;
}
return true;
}
};
TypeId Unifier2::mkUnion(TypeId left, TypeId right) TypeId Unifier2::mkUnion(TypeId left, TypeId right)
{ {
left = follow(left); left = follow(left);
@ -941,4 +748,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

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