Compare commits

...

285 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
Hunter Goldstein
e0b55a9cb1
Sync to upstream/release/665 (#1732)
Hello all! Another week, another Luau release!

# Change to `lua_setuserdatametatable`

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

# General

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

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

# Fragment Autocomplete
* Fixed several crashes related to not properly filling in scope
information for the fragments
* Fixed a source of memory corruption by isolating the return type of a
fragment when it is type checked.
* Improved performance by opting not to clone persistent types for the
fragment (e.g.: built in types)
 
# Internal Contributors
Co-authored-by: Andy Friesen <afriesen@roblox.com>
Co-authored-by: Hunter Goldstein <hgoldstein@roblox.com>
Co-authored-by: Talha Pathan <tpathan@roblox.com>
Co-authored-by: Varun Saini <vsaini@roblox.com>
Co-authored-by: Vighnesh Vijay <vvijay@roblox.com>
Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>

---------

Co-authored-by: Varun Saini <61795485+vrn-sn@users.noreply.github.com>
Co-authored-by: Alexander Youngblood <ayoungblood@roblox.com>
Co-authored-by: Menarul Alam <malam@roblox.com>
Co-authored-by: Aviral Goel <agoel@roblox.com>
Co-authored-by: Vighnesh <vvijay@roblox.com>
Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
Co-authored-by: Ariel Weiss <aaronweiss@roblox.com>
2025-03-14 13:11:24 -07:00
Kostadin
b0c3f40b0c
Add #include <stdint.h> to fix building with gcc 15 (#1720)
With gcc 15, the C++ Standard Library no longer includes other headers
that were internally used by the library. In Luau's case the missing
header is `<stdint.h>`

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

---------

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

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

## New Type Solver

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

## Fragment Autocomplete

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

## Internal Contributors

Co-authored-by: Andy Friesen <afriesen@roblox.com>
Co-authored-by: Hunter Goldstein <hgoldstein@roblox.com>
Co-authored-by: Vighnesh Vijay <vvijay@roblox.com>
Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
2025-03-07 10:07:27 -08:00
ariel
640ebbc0a5
Sync to upstream/release/663 (#1699)
Hey folks, another week means another Luau release! This one features a
number of bug fixes in the New Type Solver including improvements to
user-defined type functions and a bunch of work to untangle some of the
outstanding issues we've been seeing with constraint solving not
completing in real world use. We're also continuing to make progress on
crashes and other problems that affect the stability of fragment
autocomplete, as we work towards delivering consistent, low-latency
autocomplete for any editor environment.

## New Type Solver

- Fix a bug in user-defined type functions where `print` would
incorrectly insert `\1` a number of times.
- Fix a bug where attempting to refine an optional generic with a type
test will cause a false positive type error (fixes #1666)
- Fix a bug where the `refine` type family would not skip over
`*no-refine*` discriminants (partial resolution for #1424)
- Fix a constraint solving bug where recursive function calls would
consistently produce cyclic constraints leading to incomplete or
inaccurate type inference.
- Implement `readparent` and `writeparent` for class types in
user-defined type functions, replacing the incorrectly included `parent`
method.
- Add initial groundwork (under a debug flag) for eager free type
generalization, moving us towards further improvements to constraint
solving incomplete errors.

## Fragment Autocomplete

- Ease up some assertions to improve stability of mixed-mode use of the
two type solvers (i.e. using Fragment Autocomplete on a type graph
originally produced by the old type solver)
- Resolve a bug with type compatibility checks causing internal compiler
errors in autocomplete.

## Lexer and Parser

- Improve the accuracy of the roundtrippable AST parsing mode by
correctly placing closing parentheses on type groupings.
- Add a getter for `offset` in the Lexer by @aduermael in #1688
- Add a second entry point to the parser to parse an expression,
`parseExpr`

## 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: James McNellis <jmcnellis@roblox.com>
Co-authored-by: Talha Pathan <tpathan@roblox.com>
Co-authored-by: Vighnesh Vijay <vvijay@roblox.com>
Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>

---------

Co-authored-by: Hunter Goldstein <hgoldstein@roblox.com>
Co-authored-by: Varun Saini <61795485+vrn-sn@users.noreply.github.com>
Co-authored-by: Alexander Youngblood <ayoungblood@roblox.com>
Co-authored-by: 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>
2025-02-28 14:42:30 -08:00
Adrien Duermael
6a21dba682
Lexer: add offset getter (#1688)
Added a getter for the Lexer's private `offset` to track its cursor
position in the buffer.
This helps me index tokens by buffer address in a project where I'm
rendering Luau code with [Dear ImGui](https://github.com/ocornut/imgui).
Would love this merged so I can use official Luau releases again!

Co-authored-by: Adrian Duermael <adrien@cu.bzh>
2025-02-24 16:25:57 -08:00
ramdoys
c1e2f650db
chore: update applicable .lua files to .luau (#1560)
Updates all of the APPLICABLE .lua files to the .luau file ending.

---------

Co-authored-by: ramdoys <ramdoysdirect@gmail.com>
2025-02-21 14:29:20 -08:00
vegorov-rbx
c2e72666d9
Sync to upstream/release/662 (#1681)
## What's new

This update brings improvements to the new type solver, roundtrippable
AST parsing mode and closes multiple issues reported in this repository.

* `require` dependency tracing for non-string requires now supports `()`
groups in expressions and types as well as an ability to type annotate a
value with a `typeof` of a different module path
* Fixed rare misaligned memory access in Compiler/Typechecker on 32 bit
platforms (Closes #1572)

## New Solver

* Fixed crash/UB in subtyping of type packs (Closes #1449)
* Fixed incorrect type errors when calling `debug.info` (Closes #1534
and Resolves #966)
* Fixed incorrect boolean and string equality comparison result in
user-defined type functions (Closes #1623)
* Fixed incorrect class types being produced in user-defined type
functions when multiple classes share the same name (Closes #1639)
* Improved bidirectional typechecking for table literals containing
elements that have not been solved yet (Closes #1641)

## Roundtrippable AST

* Added source information for `AstStatTypeAlias`
* Fixed an issue with `AstTypeGroup` node (added in #1643) producing
invalid AST json. Contained type is now named 'inner' instead of 'type'
* Fixed end location of the `do ... end` statement

---

Internal Contributors:

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>
2025-02-21 10:24:12 -08:00
ariel
86bf4ae42d
Revise some of the copytext in markdown files. (#1677)
We have a bunch of small grammatical nits and slightly awkward phrasings
present in our existing markdown files. This is a small pass over all of
them to fix those, and to provide some additional updated information
that has become more clear over time (like additional users of Luau, or
our leveraging something akin to the Minus 100 Points philosophy for
evaluating RFCs).

---------

Co-authored-by: Varun Saini <61795485+vrn-sn@users.noreply.github.com>
2025-02-20 13:56:00 -08:00
karl-police
29a5198055
Fix the order of the arguments being inputted into result.append in print() from the Type Function Runtime (#1676)
---

Co-authored-by: vegorov-rbx <75688451+vegorov-rbx@users.noreply.github.com>
2025-02-20 12:32:47 -08:00
vegorov-rbx
14ccae9b44
Update minimal Ubuntu version in workflows from 20.04 to 22.04 (#1670)
Ubuntu 20.04 LTS is reaching the end of its standard support period.
GitHub runners are going to stop providing images for it soon.

This PR updates all workflows to use 22.04 instead of 20.04.
While this will impact the compatibility of prebuilt release binaries on
20.04, users of those systems can always build Luau from source.

`coverage` workflow is also updating from clang++10 to clang++ (defaults
to clang++14 today).
Line coverage with this switch drops from 89.65% to 87.45%, function
coverage remains 91.26%.
As a bonus, we now get branch coverage information.
2025-02-20 10:32:33 -08:00
nothing
9c198413ec
Analysis: Make typeof on a type userdata return "type" (#1568)
This change introduces a flag (`LuauUserTypeFunTypeofReturnsType`) that,
when enabled, sets `__type` on the type userdata's metatable to "type".
This behaviour was described in the user-defined type function RFC
(https://rfcs.luau.org/user-defined-type-functions.html), but seems to
have been missed; this change implements that behaviour.

Currently this does not change `typeof(t) == 'type'` emitting an unknown
type warning as I don't trust myself to implement it due to my general
lack of C++ knowledge; this can be worked on later.
2025-02-17 09:36:52 -08:00
dependabot[bot]
bd4fe54f4b
Bump jinja2 from 3.1.4 to 3.1.5 in /tools/fuzz (#1607) 2025-02-17 08:58:48 -08:00
Vighnesh-V
77642988c2
Sync to upstream/release/661 (#1664)
# General
- Additional logging enabled for fragment autocomplete.

## Roundtrippable AST
- Add a new `AstNode`, `AstGenericType`
- Retain source information for `AstExprTypeAssertion`
## New Type Solver
- New non-strict mode will report unknown symbol errors, e.g
```
foo = 5
local wrong1 = foob <- issue warning
```
- Fixed a bug where new non-strict mode failed to visit large parts of
the program.
- We now infer the types of unnanotated local variables in statements
with multiple assignments, e.g. `local x: "a", y, z = "a", f()`
- Fixed bugs in constraint dispatch ordering.
- Fixed a bug that caused an infinite loop between `Subtyping`,
`OverloadResolution`, and `Type Function Reduction`, by preventing calls
to `Type Function Reduction` being re-entrant.
- Fixed a crash in bidirectional type inference caused by asserting read
and write properties on a type that was readonly.

## Runtime
- Fix a stack overflow caused by `luaL_checkstack` consuming stack space
even if the function fails to reserve memory.
- Using '%c' with a 0 value in Luau string.format will append a '\0'.
Resolves https://github.com/luau-lang/luau/issues/1650

## Miscellaneous
- Miscellaneous small bugfixes for the new solver.

**Full Changelog**:
https://github.com/luau-lang/luau/compare/0.660...0.661
----
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: Vyacheslav Egorov <vegorov@roblox.com>

---------

Co-authored-by: Hunter Goldstein <hgoldstein@roblox.com>
Co-authored-by: Varun Saini <61795485+vrn-sn@users.noreply.github.com>
Co-authored-by: Alexander Youngblood <ayoungblood@roblox.com>
Co-authored-by: Menarul Alam <malam@roblox.com>
Co-authored-by: Aviral Goel <agoel@roblox.com>
2025-02-14 13:57:46 -08:00
Aviral Goel
2e61028cba
Sync to upstream/release/660 (#1643)
# General

This release introduces initial work on a Roundtrippable AST for Luau,
and numerous fixes to the new type solver, runtime, and fragment
autocomplete.

## Roundtrippable AST

To support tooling around source code transformations, we are extending
the parser to retain source information so that we can re-emit the
initial source code exactly as the author wrote it. We have made
numerous changes to the Transpiler, added new AST types such as
`AstTypeGroup`, and added source information to AST nodes such as
`AstExprInterpString`, `AstExprIfElse`, `AstTypeTable`,
`AstTypeReference`, `AstTypeSingletonString`, and `AstTypeTypeof`.

## New Type Solver

* Implement `setmetatable` and `getmetatable` type functions.
* Fix handling of nested and recursive union type functions to prevent
the solver from getting stuck.
* Free types in both old and new solver now have an upper and lower
bound to resolve mixed mode usage of the solvers in fragment
autocomplete.
* Fix infinite recursion during normalization of cyclic tables.
* Add normalization support for intersections of subclasses with negated
superclasses.

## Runtime
* Fix compilation error in Luau buffer bit operations for big-endian
machines.

## Miscellaneous
* Add test and bugfixes to fragment autocomplete.
* Fixed `clang-tidy` warnings in `Simplify.cpp`.

**Full Changelog**:
https://github.com/luau-lang/luau/compare/0.659...0.660

---

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: Vyacheslav Egorov <vegorov@roblox.com>

---------

Co-authored-by: Hunter Goldstein <hgoldstein@roblox.com>
Co-authored-by: Varun Saini <61795485+vrn-sn@users.noreply.github.com>
Co-authored-by: Alexander Youngblood <ayoungblood@roblox.com>
Co-authored-by: Menarul Alam <malam@roblox.com>
2025-02-07 16:17:11 -08:00
menarulalam
f8a1e0129d
Sync to upstream/release/659 (#1637)
## What's Changed

General performance improvements and bug fixes. `lua_clonetable` was
added too.

### General

## Runtime
- Improvements were made to Luau's performance, including a
`lua_clonetable` function and optimizations to string caching. Buffer
read/write operations were optimized for big-endian machines.
## New Solver
- Crashes related to duplicate keys in table literals, fragment AC
crashes, and potential hash collisions in the StringCache.
- We now handle user-defined type functions as opaque and track interior
free table types.
## Require By String
- Require-by-string path resolution was simplified.

**Full Changelog**:
https://github.com/luau-lang/luau/compare/0.658...0.659

---

Co-authored-by: Ariel Weiss <aaronweiss@roblox.com>
Co-authored-by: Andy Friesen <afriesen@roblox.com>
Co-authored-by: Hunter Goldstein <hgoldstein@roblox.com>
Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
Co-authored-by: Varun Saini <vsaini@roblox.com>
Co-authored-by: Vighnesh Vijay <vvijay@roblox.com>
Co-authored-by: Yohoo Lin <yohoo@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>
2025-01-31 18:58:36 -08:00
ayoungbloodrbx
c13b5b7440
Sync to upstream/release/658 (#1625)
Some checks failed
benchmark / callgrind (map[branch:main name:luau-lang/benchmark-data], ubuntu-22.04) (push) Has been cancelled
build / macos (push) Has been cancelled
build / macos-arm (push) Has been cancelled
build / ubuntu (push) Has been cancelled
build / windows (Win32) (push) Has been cancelled
build / windows (x64) (push) Has been cancelled
build / coverage (push) Has been cancelled
build / web (push) Has been cancelled
release / macos (push) Has been cancelled
release / ubuntu (push) Has been cancelled
release / windows (push) Has been cancelled
release / web (push) Has been cancelled
## What's Changed

### General
- Allow types of tables to diverge after using `table.clone` (fixes
#1617).
- Allow 2-argument vector.create in Luau.
- Fix a crash when suggesting autocomplete after encountering parsing
errors.
- Add lua_tolstringatom C API which returns the string length (whether
or not the atom exists) and which extends the existing lua_tostringatom
function the same way lua_tolstring/lua_tostring do.
- Luau now retains the DFGs of typechecked modules.

### Magic Functions Migration Note
We've made a change to the API used to define magic functions.

Previously, we had a set of function pointers on each `FunctionType`
that would be invoked by the type inference engine at the correct point.

The problem we'd run into is that they were all `std::function`s, we'd
grown quite a few of them, and Luau allocates tens of thousands of types
as it performs type inference. This adds up to a large amount of memory
for data that isn't used by 99% of types.

To slim things down a bit, we've replaced all of those `std::function`s
with a single `shared_ptr` to a new interface called `MagicFunction`.
This slims down the memory footprint of each type by about 50 bytes.

The virtual methods of `MagicFunction` have roughly 1:1 correspondence
with the old interface, so updating things should not be too difficult:

* `FunctionType::magicFunction` is now `MagicFunction::handleOldSolver`
* `FunctionType::dcrMagicFunction` is now `MagicFunction::infer`
* `FunctionType::dcrMagicRefinement` is now `MagicFunction::refine`
* `FunctionType::dcrMagicTypeCheck` is now `MagicFunction::typeCheck`

**Full Changelog**:
https://github.com/luau-lang/luau/compare/0.657...0.658

---

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: 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-01-24 12:15:19 -08:00
vegorov-rbx
6061a14e9f
Fixup desync between internal and external codebases (#1622)
Some checks failed
benchmark / callgrind (map[branch:main name:luau-lang/benchmark-data], ubuntu-22.04) (push) Has been cancelled
build / macos (push) Has been cancelled
build / macos-arm (push) Has been cancelled
build / ubuntu (push) Has been cancelled
build / windows (Win32) (push) Has been cancelled
build / windows (x64) (push) Has been cancelled
build / coverage (push) Has been cancelled
build / web (push) Has been cancelled
release / macos (push) Has been cancelled
release / ubuntu (push) Has been cancelled
release / windows (push) Has been cancelled
release / web (push) Has been cancelled
During conflict resolution while transferring patches back and forth,
some conflict resolutions may cause differences that are harder to spot.

This process would have also cleaned up differences noted in
https://github.com/luau-lang/luau/pull/1621
2025-01-20 08:05:58 -08:00
Arseny Kapoulkine
82c9383a39
Fix math.map/math.lerp merge fallout (#1621)
Some checks are pending
benchmark / callgrind (map[branch:main name:luau-lang/benchmark-data], ubuntu-22.04) (push) Waiting to run
build / macos (push) Waiting to run
build / macos-arm (push) Waiting to run
build / ubuntu (push) Waiting to run
build / windows (Win32) (push) Waiting to run
build / windows (x64) (push) Waiting to run
build / coverage (push) Waiting to run
build / web (push) Waiting to run
release / macos (push) Waiting to run
release / ubuntu (push) Waiting to run
release / windows (push) Waiting to run
release / web (push) Waiting to run
- LuauMathMap flag was not removed but the uses of it were
- LuauMathLerp addition to math table was duplicated
2025-01-19 14:14:12 -08:00
Varun Saini
29047504da
Sync to upstream/release/657 (#1619)
Some checks failed
benchmark / callgrind (map[branch:main name:luau-lang/benchmark-data], ubuntu-22.04) (push) Has been cancelled
build / macos (push) Has been cancelled
build / macos-arm (push) Has been cancelled
build / ubuntu (push) Has been cancelled
build / windows (Win32) (push) Has been cancelled
build / windows (x64) (push) Has been cancelled
build / coverage (push) Has been cancelled
build / web (push) Has been cancelled
release / macos (push) Has been cancelled
release / ubuntu (push) Has been cancelled
release / windows (push) Has been cancelled
release / web (push) Has been cancelled
## General
- Fix a parsing bug related to the starting position of function names.
- Rename Luau's `Table` struct to `LuaTable`.

## New Solver
- Add support for generics in user-defined type functions
([RFC](https://rfcs.luau.org/support-for-generic-function-types-in-user-defined-type-functions.html)).
- Provide a definition of `math.lerp` to the typechecker.
- Implement error suppression in `string.format`.
  - Fixes #1587.
- Ensure function call discriminant types are always filled when
resolving `FunctionCallConstraint`.

---

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: Vyacheslav Egorov <vegorov@roblox.com>
2025-01-17 14:55:39 -08:00
Petri Häkkinen
67e9d85124
Add 2-component vector constructor (#1569)
Some checks are pending
benchmark / callgrind (map[branch:main name:luau-lang/benchmark-data], ubuntu-22.04) (push) Waiting to run
build / macos (push) Waiting to run
build / macos-arm (push) Waiting to run
build / ubuntu (push) Waiting to run
build / windows (Win32) (push) Waiting to run
build / windows (x64) (push) Waiting to run
build / coverage (push) Waiting to run
build / web (push) Waiting to run
release / macos (push) Waiting to run
release / ubuntu (push) Waiting to run
release / windows (push) Waiting to run
release / web (push) Waiting to run
Implement RFC: 2-component vector constructor. This includes 2-component
overload for `vector.create` and associated fastcall function, and its
type definition. These features are controlled by a new feature flag
`LuauVector2Constructor`. Additionally constant folding now supports two
components when `LuauVector2Constants` feature flag is set.

Note: this work does not include changes to CodeGen. Thus calls to
`vector.create` with only two arguments are not natively compiled
currently. This is left for future work.
2025-01-17 08:45:03 -08:00
Arseny Kapoulkine
24cacc94ed
CodeGen: Implement support for math.lerp lowering (#1609)
Some checks are pending
benchmark / callgrind (map[branch:main name:luau-lang/benchmark-data], ubuntu-22.04) (push) Waiting to run
build / macos (push) Waiting to run
build / macos-arm (push) Waiting to run
build / ubuntu (push) Waiting to run
build / windows (Win32) (push) Waiting to run
build / windows (x64) (push) Waiting to run
build / coverage (push) Waiting to run
build / web (push) Waiting to run
release / macos (push) Waiting to run
release / ubuntu (push) Waiting to run
release / windows (push) Waiting to run
release / web (push) Waiting to run
To implement math.lerp without branches, we add SELECT_NUM which
selects one of the two inputs based on the comparison condition.

For simplicity, we only support C == D for now; this can be extended to
a more generic version with a IrCondition operand E, but that requires
more work on the SSE side (to flip the comparison for some conditions
like Greater, and expose more generic vcmpsd).

Note: On AArch64 this will effectively result in a change in floating
point
behavior between native code and non-native code: clang synthesizes
fmadd (because floating point contraction is allowed by default, and the
arch always has the instruction), whereas this change will use
fmul+fadd.

I am not sure if this is good or bad, and if this is a problem in C or
not.
Specifically, clang's behavior results in different results between X64
and AArch64 when *not* using codegen, and with this change the behavior
when using codegen is... the same? :)

Fixing this will require either using LERP_NUM instead and hand-coding
lowering, or exposing some sort of "quasi" MADD_NUM (which would
lower to fma on AArch64 and mul+add on X64).

A small benefit to the current approach is `lerp(1, 5, t)`
constant-folds the
subtraction. With LERP_NUM this optimization will need to be implemented
manually as a partial constant-folding for LERP_NUM.

A similar problem exists today for vector.cross & vector.dot. So maybe
this
is not something we need to fix, unsure.
2025-01-16 10:48:27 -08:00
Hunter Goldstein
c759cd5581
Sync to upstream/release/656 (#1612)
Some checks failed
benchmark / callgrind (map[branch:main name:luau-lang/benchmark-data], ubuntu-22.04) (push) Has been cancelled
build / macos (push) Has been cancelled
build / macos-arm (push) Has been cancelled
build / ubuntu (push) Has been cancelled
build / windows (Win32) (push) Has been cancelled
build / windows (x64) (push) Has been cancelled
build / coverage (push) Has been cancelled
build / web (push) Has been cancelled
release / macos (push) Has been cancelled
release / ubuntu (push) Has been cancelled
release / windows (push) Has been cancelled
release / web (push) Has been cancelled
# General

All code has been re-formatted by `clang-format`; this is not
mechanically enforced, so Luau may go out-of-sync over the course of the
year.

# New Solver

* Track free types interior to a block of code on `Scope`, which should
reduce the number of free types that remain un-generalized after type
checking is complete (e.g.: less errors like `'a <: number is
incompatible with number`).

# Autocomplete

* Fragment autocomplete now does *not* provide suggestions within
comments (matching non-fragment autocomplete behavior).
* Autocomplete now respects iteration and recursion limits (some hangs
will now early exit with a "unification too complex error," some crashes
will now become internal complier exceptions).

# Runtime

* Add a limit to how many Luau codegen slot nodes addresses can be in
use at the same time (fixes #1605, fixes #1558).
* Added constant folding for vector arithmetic (fixes #1553).
* Added support for `buffer.readbits` and `buffer.writebits` (see:
https://github.com/luau-lang/rfcs/pull/18).

---

Co-authored-by: Aaron Weiss <aaronweiss@roblox.com>
Co-authored-by: David Cope <dcope@roblox.com>
Co-authored-by: Hunter Goldstein <hgoldstein@roblox.com>
Co-authored-by: Vighnesh Vijay <vvijay@roblox.com>
Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
2025-01-10 11:34:39 -08:00
Vyacheslav Egorov
945c510b3c Merge branch 'merge' 2025-01-10 17:59:33 +02:00
Arseny Kapoulkine
8a4ef26f89
Implement support for math.lerp (#1608)
Some checks are pending
benchmark / callgrind (map[branch:main name:luau-lang/benchmark-data], ubuntu-22.04) (push) Waiting to run
build / macos (push) Waiting to run
build / macos-arm (push) Waiting to run
build / ubuntu (push) Waiting to run
build / windows (Win32) (push) Waiting to run
build / windows (x64) (push) Waiting to run
build / coverage (push) Waiting to run
build / web (push) Waiting to run
release / macos (push) Waiting to run
release / ubuntu (push) Waiting to run
release / windows (push) Waiting to run
release / web (push) Waiting to run
This change implements math.lerp RFC with C function definition, builtin
function, builtin constant folding and tests.

The tests validate a few lerp properties by providing counter-examples
for popular lerp implementations; the testing is of course not
exhaustive, as exhaustive testing was done offline using fuzzing.

Type definitions will be updated separately.

Codegen support will be implemented separately: it requires new IR for
conditional
selects to represent the desired logic without using a branch.
2025-01-09 09:42:07 -08:00
vegorov-rbx
9a102e2aff
Fix negation type 'inner' method in user-defined type functions (#1582)
Some checks failed
benchmark / callgrind (map[branch:main name:luau-lang/benchmark-data], ubuntu-22.04) (push) Has been cancelled
build / macos (push) Has been cancelled
build / macos-arm (push) Has been cancelled
build / ubuntu (push) Has been cancelled
build / windows (Win32) (push) Has been cancelled
build / windows (x64) (push) Has been cancelled
build / coverage (push) Has been cancelled
build / web (push) Has been cancelled
release / macos (push) Has been cancelled
release / ubuntu (push) Has been cancelled
release / windows (push) Has been cancelled
release / web (push) Has been cancelled
Fixes #1580
2024-12-20 11:30:43 -08:00
aaron
8f94786ceb
Refactor CLI structure to match the include/src split that our other projects have. (#1573)
Some checks failed
benchmark / callgrind (map[branch:main name:luau-lang/benchmark-data], ubuntu-22.04) (push) Has been cancelled
build / macos (push) Has been cancelled
build / macos-arm (push) Has been cancelled
build / ubuntu (push) Has been cancelled
build / windows (Win32) (push) Has been cancelled
build / windows (x64) (push) Has been cancelled
build / coverage (push) Has been cancelled
build / web (push) Has been cancelled
release / macos (push) Has been cancelled
release / ubuntu (push) Has been cancelled
release / windows (push) Has been cancelled
release / web (push) Has been cancelled
This PR refactors the CLI folder to use the same project split between
include and src directories that we have for all the other artifacts in
luau. It also includes the require-by-string implementation we already
have as a feature of `Luau.CLI.lib`. Both of these changes are targeted
at making it easier for embedding projects to setup an effective
equivalent to the standalone `luau` executable with whatever runtime
libraries they need attached and without having to unnecessarily
duplicate code from luau itself.
2024-12-17 13:50:27 -08:00
vegorov-rbx
7ab3482003
Remove Ast dependency on CLI.Lib (#1571)
Some checks are pending
benchmark / callgrind (map[branch:main name:luau-lang/benchmark-data], ubuntu-22.04) (push) Waiting to run
build / macos (push) Waiting to run
build / macos-arm (push) Waiting to run
build / ubuntu (push) Waiting to run
build / windows (Win32) (push) Waiting to run
build / windows (x64) (push) Waiting to run
build / coverage (push) Waiting to run
build / web (push) Waiting to run
release / macos (push) Waiting to run
release / ubuntu (push) Waiting to run
release / windows (push) Waiting to run
release / web (push) Waiting to run
2024-12-16 16:40:46 -08:00
Vighnesh-V
2e6fdd90a0
Sync to upstream/release/655 (#1563)
Some checks failed
benchmark / callgrind (map[branch:main name:luau-lang/benchmark-data], ubuntu-22.04) (push) Has been cancelled
build / macos (push) Has been cancelled
build / macos-arm (push) Has been cancelled
build / ubuntu (push) Has been cancelled
build / windows (Win32) (push) Has been cancelled
build / windows (x64) (push) Has been cancelled
build / coverage (push) Has been cancelled
build / web (push) Has been cancelled
release / macos (push) Has been cancelled
release / ubuntu (push) Has been cancelled
release / windows (push) Has been cancelled
release / web (push) Has been cancelled
## New Solver
* Type functions should be able to signal whether or not irreducibility
is due to an error
* Do not generate extra expansion constraint for uninvoked user-defined
type functions
* Print in a user-defined type function reports as an error instead of
logging to stdout
* Many e-graphs bugfixes and performance improvements
* Many general bugfixes and improvements to the new solver as a whole
* Fixed issue with used-defined type functions not being able to call
each other
* Infer types of globals under new type solver

## Fragment Autocomplete
* Miscellaneous fixes to make interop with the old solver better

## Runtime
* Support disabling specific built-in functions from being fast-called
or constant-evaluated (Closes #1538)
* New compiler option `disabledBuiltins` accepts a list of library
function names like "tonumber" or "math.cos"
* Added constant folding for vector arithmetic
* Added constant propagation and type inference for vector globals
(Fixes #1511)
* New compiler option `librariesWithKnownMembers` accepts a list of
libraries for members of which a request for constant value and/or type
will be made
* `libraryMemberTypeCb` callback is called to get the type of a global,
return one of the `LuauBytecodeType` values. 'boolean', 'number',
'string' and 'vector' type are supported.
* `libraryMemberConstantCb` callback is called to setup the constant
value of a global. To set a value, C API `luau_set_compile_constant_*`
or C++ API `setCompileConstant*` functions should be used.

---
Co-authored-by: Aaron Weiss <aaronweiss@roblox.com>
Co-authored-by: Andy Friesen <afriesen@roblox.com>
Co-authored-by: Aviral Goel <agoel@roblox.com>
Co-authored-by: Daniel Angel <danielangel@roblox.com>
Co-authored-by: Jonathan Kelaty <jkelaty@roblox.com>
Co-authored-by: Hunter Goldstein <hgoldstein@roblox.com>
Co-authored-by: Varun Saini <vsaini@roblox.com>
Co-authored-by: Vighnesh Vijay <vvijay@roblox.com>
Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>

---------

Co-authored-by: Aaron Weiss <aaronweiss@roblox.com>
Co-authored-by: Alexander McCord <amccord@roblox.com>
Co-authored-by: Andy Friesen <afriesen@roblox.com>
Co-authored-by: Aviral Goel <agoel@roblox.com>
Co-authored-by: David Cope <dcope@roblox.com>
Co-authored-by: Lily Brown <lbrown@roblox.com>
Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
Co-authored-by: Junseo Yoo <jyoo@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: Varun Saini <vsaini@roblox.com>
Co-authored-by: Andrew Miranti <amiranti@roblox.com>
Co-authored-by: Shiqi Ai <sai@roblox.com>
Co-authored-by: Yohoo Lin <yohoo@roblox.com>
Co-authored-by: Daniel Angel <danielangel@roblox.com>
Co-authored-by: Jonathan Kelaty <jkelaty@roblox.com>
2024-12-13 13:02:30 -08:00
Vighnesh
87eac7befa Merge branch 'upstream' into merge 2024-12-13 11:21:49 -08:00
Vighnesh
79cdfe1094 Merge branch 'master' into merge 2024-12-13 11:21:40 -08:00
Vighnesh
906a00d498 Sync to upstream/release/655
* General
- Fix the benchmark require wrapper function to work in Lua
- Fix memory leak in the new Luau C API test

* New Solver
- Luau: type functions should be able to signal whether or not irreducibility is due to an error
- Do not generate extra expansion constraint for uninvoked user-defined type functions
- Print in a user-defined type function should be reported as an error
instead of logging to stdout
- Many e-graphs bugfixes and performance improvements
- Many general bugfixes and improvements to the new solver as a whole
- Fixed issue with Luau used-defined type functions not having all environments initialized
- Infer types of globals under new type solver

* Fragment Autocomplete
- Miscellaneous fixes to make interop with the old solver better

* Runtime
- Support disabling specific Luau built-in functions from being
fast-called or constant-evaluated
- Added constant folding for vector arithmetic
- Added constant propagation and type inference for Vector3 globals

----------------------------------------------------------
9 contributors:

Co-authored-by: Aaron Weiss <aaronweiss@roblox.com>
Co-authored-by: Andy Friesen <afriesen@roblox.com>
Co-authored-by: Aviral Goel <agoel@roblox.com>
Co-authored-by: Daniel Angel <danielangel@roblox.com>
Co-authored-by: Jonathan Kelaty <jkelaty@roblox.com>
Co-authored-by: Hunter Goldstein <hgoldstein@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>
2024-12-13 11:20:43 -08:00
jkelaty-rbx
8b8118b027
Convert Luau heap dumps to Chrome heap snapshots (#1554)
Adds a script for (approximately) converting Luau heap dumps to Chrome
heap snapshots. Useful for visually inspecting a heap dump within
Chrome's UI.
2024-12-06 10:04:57 -08:00
Aviral Goel
d0222bb554
Sync to upstream/release/654 (#1552)
# What's Changed

* Support dead store elimination for `STORE_VECTOR` instruction
* Fix parser hang when a separator is used between Luau class
declaration properties
* Provide properties and metatable for built-in vector type definition
to fix type errors
* Fix Fragment Autocomplete to ensure correct parentheses insertion
behavior.
* Add support for 'thread' and 'buffer' primitive types in user-defined
type functions

---------

Co-authored-by: Andy Friesen <afriesen@roblox.com>
Co-authored-by: Hunter Goldstein <hgoldstein@roblox.com>
Co-authored-by: Vighnesh Vijay <vvijay@roblox.com>
Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
2024-12-02 16:16:33 -08:00
Aviral Goel
dfdcff0897 Merge branch 'upstream' into merge 2024-12-02 15:11:46 -08:00
Aviral Goel
341aa38768 Merge branch 'master' into merge 2024-12-02 15:04:30 -08:00
Aviral Goel
230ab81326 Sync to upstream/release/654
* Luau: support dead store elimination for STORE_VECTOR instruction
* Fixed hang when Luau class declaration props are incorrectly separated
* Provide properties and a metatable for Luau built-in vector type
* Pick the correct global scope based on the solver
* Conversational AI gets all required scripts as context
* Clip LuauRequireCyclesDontAlwaysReturnAny
* Fix Parentheses in Fragment Autocomplete
* Remove write-only locals in `Luau::getDocumentOffsets`
* The lexer can resume parsing from any arbitrary position
* Added support for 'thread' and 'buffer' primitive types in Luau user-defined type functions

---------

Co-authored-by: Andrew Miranti <amiranti@roblox.com>
Co-authored-by: Andy Friesen <afriesen@roblox.com>
Co-authored-by: Hunter Goldstein <hgoldstein@roblox.com>
Co-authored-by: Shiqi Ai <sai@roblox.com>
Co-authored-by: Vighnesh Vijay <vvijay@roblox.com>
Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
Co-authored-by: Yohoo Lin <yohoo@roblox.com>
2024-12-02 15:02:24 -08:00
ramdoys
8cc289fae4
Replace parser test getParseError function for matchParseError (#1532)
Removes the getParseError function in Parser.test.cpp to use
matchParseError instead.
2024-11-27 07:34:14 -08:00
Arseny Kapoulkine
8f2ab4cbad
Minor tweak to FASTCALL3 instruction (#1548)
In all other places, L->top is extracted to a local when writing to
stack; this helps compilers without TBAA (MSVC) to not reload L->top
redundantly.

Also assert that we do in fact have 2 slots of stack space (which we
do).
2024-11-27 07:07:37 -08:00
Arseny Kapoulkine
b5801d3377
CodeGen: Optimize arithmetics for basic identities (#1545)
This change folds:

	a * 1 => a
	a / 1 => a
	a * -1 => -a
	a / -1 => -a
	a * 2 => a + a
	a / 2^k => a * 2^-k
	a - 0 => a
	a + (-0) => a

Note that the following folds are all invalid:

	a + 0 => a (breaks for negative zero)
	a - (-0) => a (breaks for negative zero)
	a - a => 0 (breaks for Inf/NaN)
	0 - a => -a (breaks for negative zero)

Various cases of UNM_NUM could be optimized (eg (-a) * (-b) = a * b),
but that doesn't happen in benchmarks.

While it would be possible to also fold inverse multiplications (k * v),
these do not happen in benchmarks and rarely happen in bytecode due
to type based optimizations. Maybe this can be improved with some sort
of
IR canonicalization in the future if necessary.

I've considered moving some of these, like division strength reduction,
to IR translation (as this is where POW is lowered presently) but it
didn't
seem better one way or the other.

This change improves performance on some benchmarks, e.g. trig and
voxelgen,
and should be a strict uplift as it never generates more instructions or
longer
latency chains. On Apple M2, without division->multiplication
optimization, both
benchmarks see 0.1-0.2% uplift. Division optimization makes trig 3%
faster; I expect
the gains on X64 will be more muted, but on Apple this seems to allow
loop iterations
to overlap better by removing the division bottleneck.
2024-11-27 04:44:39 -08:00
ayoungbloodrbx
d19a5f0699
Sync to upstream/release/653 (#1541)
## What's Changed?

* Optimized the vector dot product by up to 24%
* Allow for x/y/z/X/Y/Z vector field access by registering a `vector`
metatable
with an `__index` method (Fixes #1521)
* Fixed a bug preventing consistent recovery from parse errors in table
types.
* Optimized `k*n` and `k+n` when types are known
* Allow fragment autocomplete to handle cases like the automatic
insertion of
parens, keywords, strings, etc., while maintaining a correct relative
positioning

### New Solver

* Allow for `nil` assignment to tables and classes with indexers

---------

Co-authored-by: Aaron Weiss <aaronweiss@roblox.com>
Co-authored-by: Andy Friesen <afriesen@roblox.com>
Co-authored-by: Aviral Goel <agoel@roblox.com>
Co-authored-by: Hunter Goldstein <hgoldstein@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>
2024-11-22 13:00:51 -08:00
Alexander Youngblood
4fa6e97caa Merge branch 'upstream' into merge 2024-11-22 12:39:19 -08:00
Alexander Youngblood
0bd9321957 Sync to upstream/release/653
## What's Changed?

* Optimized the vector dot product by up to 24%
* Allow for x/y/z/X/Y/Z vector field access by registering a `vector` metatable
with an `__index` method
* Fixed a bug preventing consistent recovery from parse errors in table types.
* Optimized `k*n` and `k+n` when types are known
* Allow fragment autocomplete to handle cases like the automatic insertion of
parens, keywords, strings, etc., while maintaining a correct relative positioning

 ### New Solver

* Added support for 'thread' and 'buffer' primitive types in Luau user-defined
type functions
* Allow for `nil` assignment to tables and classes with indexers

---------

Co-authored-by: Aaron Weiss <aaronweiss@roblox.com>
Co-authored-by: Andy Friesen <afriesen@roblox.com>
Co-authored-by: Aviral Goel <agoel@roblox.com>
Co-authored-by: Hunter Goldstein <hgoldstein@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>
2024-11-22 12:37:17 -08:00
Alexander Youngblood
dd7a19d8ea Merge branch 'master' into merge 2024-11-22 11:42:42 -08:00
vegorov-rbx
7a6142e792
Update vector-math benchmark name to correctly display (#1539)
While we could update the `awk` regular expression, we can just make the
test name compatible.
2024-11-19 16:13:52 -08:00
Arseny Kapoulkine
b1b21f395a
Compiler: Optimize k*n and k+n when types are known (#1529)
When type information is specified, we can compile k*n and k+n into
MULK/ADDK forms that are faster to execute, as long as we think n is a
number. Since we generally restrict type aware optimizations to O2, this
does that as well.

This makes trig benchmark ~4% faster on Apple M2 in VM, and also a tiny
improvement on scimark (~0.1%) can be observed. The optimization only
affects interpreted execution, as NCG already can synthesize optimal
code here.

If the type information is not truthful (e.g. user annotates type as a
number and it's not), the worst case scenario is flipped arguments to
metamethods like __add/__mul for constant left hand side.

Fixes #626 (the fix requires type information or NCG but I doubt any
further work on this is warranted)

---------

Co-authored-by: vegorov-rbx <75688451+vegorov-rbx@users.noreply.github.com>
2024-11-19 07:42:44 -08:00
Varun Saini
c2e4ee0203
Fix benchmark runner bug introduced in release 0.652 (#1530)
### Problem

In release 0.652, `RequireResolver` was refactored to add support for
`luau-analyze`.

As part of this update, `RuntimeRequireContext` introduced a new
convention where a file's chunkname must be prefixed with `@` (e.g.,
`@./some/path.luau`). This change applies to all chunknames generated
within `RuntimeRequireContext`. However, when a `.luau` file is executed
directly from the command line (e.g., `luau ./my/script.luau`), the
chunkname is still generated with the old `=` prefix (e.g.,
`=./some/path.luau`).

Since `RuntimeRequireContext` no longer recognizes chunknames prefixed
with `=`, any attempt to directly execute a `.luau` file from the
command line fails. For example, running `luau ./my/script.luau` results
in an error stating that the context is unsupported. [This issue also
affects tools like the benchmark
runner](https://github.com/luau-lang/luau/pull/1525#issuecomment-2480454018),
which rely on direct file execution.

### Solution

Update `runFile` to replace the `=` prefix in generated chunknames with
`@`.
2024-11-18 04:20:05 -08:00
Varun Saini
e905e30570
Sync to upstream/release/652 (#1525)
## What's new?

* Add support for mixed-mode type checking, which allows modules checked
in the old type solver to be checked and autocompleted by the new one.
* Generalize `RequireResolver` to support require-by-string semantics in
`luau-analyze`.
* Fix a bug in incremental autocomplete where `DefId`s associated with
index expressions were not correctly picked up.
* Fix a bug that prevented "complex" types in generic parameters (for
example, `local x: X<(() -> ())?>`).

### Issues fixed
* #1507
* #1518

---

Internal Contributors:

Co-authored-by: Aaron Weiss <aaronweiss@roblox.com>
Co-authored-by: Andy Friesen <afriesen@roblox.com>
Co-authored-by: Hunter Goldstein <hgoldstein@roblox.com>
Co-authored-by: Varun Saini <vsaini@roblox.com>
Co-authored-by: Vighnesh Vijay <vvijay@roblox.com>
2024-11-15 14:29:30 -08:00
Varun Saini
4709fec28c Parser.test.cpp: auto (int) -> size_t 2024-11-15 13:15:39 -08:00
Varun Saini
ebd074803f Merge branch 'upstream' into merge 2024-11-15 11:58:26 -08:00
Varun Saini
3c87474398 Merge branch 'master' into merge 2024-11-15 11:51:19 -08:00
Varun Saini
f6f4d92107 Sync to upstream/release/652 2024-11-15 11:37:29 -08:00
Varun Saini
d1025d0029
Remove noexcepts from Config (#1523)
Fixes https://github.com/luau-lang/luau/issues/1515.

By removing these `noexcept`s, we guarantee that the internal call to
`std::swap` uses move semantics when a `Config` is copy-assigned.
2024-11-12 14:25:04 -08:00
Arseny Kapoulkine
53e6e4b8f0
Fix mesh-normal-vector benchmark array access (#1514)
mesh-normal-scalar correctly fills sequential values in the output for
triangle cone function, but mesh-normal-vector accidentally reuses the
loop index, which results in writes to every third index of the array
(1, 4, etc.).

This is both slower (as the table turns into a hash map), and incorrect,
especially as we have a scalar version of the benchmark that does the
right thing.

Note: there's a bunch of inefficiencies in the benchmark code that I
have not fixed (around field access mostly, e.g. writing to `v.n` and
then immediately reading it again). These are not ideal for performance,
but they can be valuable to keep as is because this redundancy is common
in real-world code, and it would be nice to see codegen optimizations
eliminating most of that overhead. This one, however, is a straight up
bug, and sparse arrays should not really be the thing this benchmark
hits.
2024-11-11 12:39:09 -08:00
Arseny Kapoulkine
e6bf71871a
CodeGen: Rewrite dot product lowering using a dedicated IR instruction (#1512)
Instead of doing the dot product related math in scalar IR, we lift the
computation into a dedicated IR instruction.

On x64, we can use VDPPS which was more or less tailor made for this
purpose. This is better than manual scalar lowering that requires
reloading components from memory; it's not always a strict improvement
over the shuffle+add version (which we never had), but this can now be
adjusted in the IR lowering in an optimal fashion (maybe even based on
CPU vendor, although that'd create issues for offline compilation).

On A64, we can either use naive adds or paired adds, as there is no
dedicated vector-wide horizontal instruction until SVE. Both run at
about the same performance on M2, but paired adds require fewer
instructions and temporaries.

I've measured this using mesh-normal-vector benchmark, changing the
benchmark to just report the time of the second loop inside
`calculate_normals`, testing master vs #1504 vs this PR, also increasing
the grid size to 400 for more stable timings.

On Zen 4 (7950X), this PR is comfortably ~8% faster vs master, while I
see neutral to negative results in #1504.
On M2 (base), this PR is ~28% faster vs master, while #1504 is only
about ~10% faster.

If I measure the second loop in `calculate_tangent_space` instead, I
get:

On Zen 4 (7950X), this PR is ~12% faster vs master, while #1504 is ~3%
faster
On M2 (base), this PR is ~24% faster vs master, while #1504 is only
about ~13% faster.

Note that the loops in question are not quite optimal, as they store and
reload various vectors to dictionary values due to inappropriate use of
locals. The underlying gains in individual functions are thus larger
than the numbers above; for example, changing the `calculate_normals`
loop to use a local variable to store the normalized vector (but still
saving the result to dictionary value), I get a ~24% performance
increase from this PR on Zen4 vs master instead of just 8% (#1504 is
~15% slower in this setup).
2024-11-08 16:23:09 -08:00
Hunter Goldstein
a36a3c41cc
Sync to upstream/release/651 (#1513)
### What's New?

* Fragment Autocomplete: a new API allows for type checking a small
fragment of code against an existing file, significantly speeding up
autocomplete performance in large files.

### New Solver

* E-Graphs have landed: this is an ongoing approach to make the new type
solver simplify types in a more consistent and principled manner, based
on similar work (see: https://egraphs-good.github.io/).
* Adds support for exporting / local user type functions (previously
they were always exported).
* Fixes a set of bugs in which the new solver will fail to complete
inference for simple expressions with just literals and operators.

### General Updates
* Requiring a path with a ".lua" or ".luau" extension will now have a
bespoke error suggesting to remove said extension.
* Fixes a bug in which whether two `Luau::Symbol`s are equal depends on
whether the new solver is enabled.

---

Internal Contributors:

Co-authored-by: Aaron Weiss <aaronweiss@roblox.com>
Co-authored-by: Andy Friesen <afriesen@roblox.com>
Co-authored-by: David Cope <dcope@roblox.com>
Co-authored-by: Hunter Goldstein <hgoldstein@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>
2024-11-08 13:41:45 -08:00
Hunter Goldstein
9dc829b584 Use size_t in getDocumentOffsets 2024-11-08 12:32:51 -08:00
Hunter Goldstein
df67e4d62c Add headers missing when compiling with GCC 2024-11-08 11:56:37 -08:00
Hunter Goldstein
af9d9ba13e Merge branch 'upstream' into merge 2024-11-08 11:35:18 -08:00
Hunter Goldstein
4399b17f95 Merge branch 'master' into merge 2024-11-08 11:33:48 -08:00
Hunter Goldstein
c799a548e4 Sync to version/652
> What's new?

* Fragment Autocomplete: a new API allows for type checking a small
  fragment of code against an existing file, significantly speeding up
  autocomplete performance in large files.

> New Solver

* E-Graphs have landed: this is an ongoing approach to make the new type solver
  simplify types in a more consistent and principled manner, based on
  similar work (e.g.: https://egraphs-good.github.io/).
* Adds support for exported / local user type functions.
* Fixes a set of bugs in which the new solver will fail to complete
  inference for simple expressions with just literals and operators.

> General

* It is now an explicit runtime error to `require` a path with a ".lua" or
  ".luau" extension, and the error message will suggest removing the extension.
  ```
  require("path/to/mymodule.lua")
  ```
* Fixes a bug in which whether two `Symbol`s are equal depends on
  whether the new solver is enabled.
2024-11-08 11:01:20 -08:00
Barış
26b2307a8b
Replace old site urls (#1505)
this PR replaces all the old site urls from luau-lang.org to luau.org
2024-11-06 15:23:33 -08:00
aaron
47543e5df1
Set the defining module even when the new solver cloned the type. (#1506)
Follow up to #1495: a small fixup for the defining module and location
to get set even when cloning was required.
2024-11-05 15:25:38 -08:00
checkraisefold
f1d4621d59
Pre-populate/duplicate check class definitions (new solver) (#1493)
Closes #1492
Tested and working with the test case in the aforementioned issue, along
with the full defs of luau-lsp with no issues or type errors

In normal Luau files, you can use type aliases and type functions before
they are declared. The same extends to declaration files, **except** in
the new solver. The old solver perfectly allows this, and in fact
intentionally adds it:
db809395bf/Analysis/src/TypeInfer.cpp (L1711-L1717)

This causes *much* headache and pain for external projects that make use
of declaration files; namely, luau-lsp generates them from MaximumADHD's
API dump, which is not ordered by dependency. This means silent
error-types popping up everywhere because types are used before they are
declared. The workaround would be to make code to manually reorder class
definitions based on their dependencies with a bunch of code, but this
is clearly not ideal, and won't work for classes dependent on each
other/recursive.

The solution used here is the same as is used for type aliases - the
name binding for the class is given a blocked type before running the
rest of constraint generation on the block. Questions remain:
- Should the logic be split off of `checkAliases`?
- Should a bound type be used, or should the (blocked) binding type be
directly emplaced with the class type? What are the ramifications of
emplacing with the bound versus the raw type? One ramification was
initially ran into through an assertion because the class
`superTy`/`parent` was bound, and several pieces of code assume it is
not, so it had to be made followed.
- Is folllowing `superTy` to set `parent` the correct workaround for the
assertions thrown, or should the code expecting `parent` to be a
ClassType without following it be modified instead to follow `parent`?
- Should `scope->privateTypeBindings` also be checked for the duplicate
error? I would presume so, since having a class with the same name as a
private alias or type function should error as well?

The extraneous whitespace changes are clang-format ones done
automatically that should've been done in the last release - I can
remove them if necessary and let another sync or OSS cleanup commit fix
it.
2024-11-05 15:21:18 -08:00
checkraisefold
9a4bc6aeb8
Fix definition module name & location (#1495)
Closes #1441 

Brings behavior to parity with the old solver by filling in
definitionLocation and definitionModuleName for Luau-consuming
programs/libraries to use.
2024-11-05 10:33:21 -08:00
Andy Friesen
c6cf1f829a Merge branch 'upstream' into merge 2024-11-01 09:47:58 -07:00
Andy Friesen
25de210308 Merge branch 'master' into merge 2024-11-01 09:47:55 -07:00
Andy Friesen
ee5b473b86 Sync to upstream/release/650 2024-11-01 09:47:10 -07:00
Aaron Weiss
e85fb91cfd Merge branch 'upstream' into merge 2024-10-25 09:46:45 -07:00
Aaron Weiss
ed05da573a Merge branch 'master' into merge 2024-10-25 09:46:40 -07:00
Aaron Weiss
1de169f006 Sync to upstream/release/649 2024-10-25 09:46:08 -07:00
Vyacheslav Egorov
ce9f1eb905 Merge branch 'upstream' into merge 2024-10-18 18:17:08 +03:00
Vyacheslav Egorov
bbd18bf0bd Merge branch 'master' into merge 2024-10-18 18:17:04 +03:00
Vyacheslav Egorov
47e3123863 Sync to upstream/release/648 2024-10-18 18:08:01 +03:00
Vighnesh
fb8c190849 replicate changes to cmakelists 2024-10-11 17:16:39 -07:00
Vighnesh
268e0b2ab7 revert differ.h change 2024-10-11 17:13:33 -07:00
Vighnesh
837bba31e4 revert cmake file + update makefile 2024-10-11 16:57:52 -07:00
Vighnesh
5d74685798 unconditially add suppressing flag 2024-10-11 16:54:35 -07:00
Vighnesh
a7324ce8fb what happens if we remove the flag 2024-10-11 16:50:14 -07:00
Vighnesh
03c9fa721e try again? 2024-10-11 16:47:27 -07:00
Vighnesh
b5d3544d18 update build flags 2024-10-11 16:39:31 -07:00
Vighnesh
0276d18314 one more try 2024-10-11 16:30:15 -07:00
Vighnesh
b1fae55636 nit differ 2024-10-11 16:02:48 -07:00
Vighnesh
50e8dd0d71 more macro crimes 2024-10-11 15:47:58 -07:00
Vighnesh
dafee8bbef nest again 2024-10-11 15:37:54 -07:00
Vighnesh
c9ff5d1c0a nest pragma 2024-10-11 15:28:51 -07:00
Vighnesh
848fa0be8e insert new lines 2024-10-11 15:23:33 -07:00
Vighnesh
b76941b678 annotate this optional use 2024-10-11 15:21:25 -07:00
Vighnesh
507832b1f0 default initialize 2024-10-11 15:05:31 -07:00
Vighnesh
68b9214b03 push same change to shared code allocator 2024-10-11 14:51:28 -07:00
Vighnesh
a197f044b5 i messed up the macro syntax again 2024-10-11 14:49:34 -07:00
Vighnesh
ef95ce5266 newline 2024-10-11 14:40:12 -07:00
Vighnesh
39899ade4f macro out the failing tests 2024-10-11 14:33:15 -07:00
Vighnesh-V
34801b9310
Delete tests/.#TypeVar.test.cpp 2024-10-11 14:17:53 -07:00
Vighnesh
70d5a0ede8 newline 2024-10-11 14:16:41 -07:00
Vighnesh
74c1ac33a9 nit - insert new lines 2024-10-11 13:53:08 -07:00
Vighnesh
ee6a45b13b more macro madness 2024-10-11 13:46:54 -07:00
Vighnesh
69776f6fc1 re-add assertion 2024-10-11 13:39:14 -07:00
Vighnesh
f52fe9f351 nit - remove shared code allocator test section that tests self move 2024-10-11 13:22:20 -07:00
Vighnesh
a02bee5acc update -Wmaybe-uninitialized 2024-10-11 13:09:26 -07:00
Vighnesh
5bb24aa888 nit: turn int osize, nsize into size_t 2024-10-11 13:01:31 -07:00
Vighnesh
38d76e658d disable maybe uninitialized error 2024-10-11 12:56:47 -07:00
Vighnesh
0e1504fa52 try a different syntax for disabling warnings 2024-10-11 12:13:24 -07:00
Vighnesh
afa4fa601e try to disable self-move warning 2024-10-11 12:04:28 -07:00
Vighnesh
912798e5c7 Merge branch 'upstream' into merge 2024-10-11 10:05:43 -07:00
Vighnesh
40a054abe8 Merge branch 'master' into merge 2024-10-11 10:02:42 -07:00
Vighnesh
aa2e5c096d Sync to upstream/release/647 2024-10-11 09:38:27 -07:00
Andy Friesen
aabaf01484 Merge branch 'upstream' into merge 2024-10-04 09:46:59 -07:00
Andy Friesen
469ba7712f Merge branch 'master' into merge 2024-10-04 09:46:55 -07:00
Andy Friesen
8531df04c9 Sync to upstream/release/646 2024-10-04 09:42:22 -07:00
Aaron Weiss
d7bbe3ff04 fix cmake for user-defined type functions 2024-09-27 10:49:00 -07:00
Aaron Weiss
a3da985449 Merge branch 'upstream' into merge 2024-09-27 10:12:18 -07:00
Aaron Weiss
e8880490d2 Merge branch 'master' into merge 2024-09-27 10:12:15 -07:00
Aaron Weiss
7a7521a7ab Sync to upstream/release/645 2024-09-27 10:11:46 -07:00
Vyacheslav Egorov
45594a571d Fixed the issue 2024-09-20 19:42:14 +03:00
Vyacheslav Egorov
48f3c85cba Maybe more? 2024-09-20 19:11:51 +03:00
Vyacheslav Egorov
0106dfaffa Maybe reduce the base 2024-09-20 18:58:46 +03:00
Vyacheslav Egorov
9655780bd3 Reduce it even more 2024-09-20 18:48:51 +03:00
Vyacheslav Egorov
66ba3608ca Smaller limit under ASAN for autocomplete_subtyping_recursion_limit 2024-09-20 18:43:56 +03:00
Vyacheslav Egorov
0d6b70b80b Merge branch 'upstream' into merge 2024-09-20 17:38:03 +03:00
Vyacheslav Egorov
d66e088af8 Merge branch 'master' into merge 2024-09-20 17:29:25 +03:00
Vyacheslav Egorov
a45eb2c9e0 Sync to upstream/release/644 2024-09-20 17:14:29 +03:00
Vighnesh
4df4a1d408 Merge branch 'upstream' into merge 2024-09-13 10:16:25 -07:00
Vighnesh
23e9fa53cd Merge branch 'master' into merge 2024-09-13 10:16:09 -07:00
Vighnesh
cd27a20223 Sync to upstream/release/643 2024-09-13 10:14:29 -07:00
Andy Friesen
ef8e1e3945 Merge branch 'upstream' into merge 2024-09-06 11:31:06 -07:00
Andy Friesen
538b0c071f Merge branch 'master' into merge 2024-09-06 11:31:02 -07:00
Andy Friesen
15557d8d01 Sync to upstream/release/642 2024-09-06 11:30:31 -07:00
Aaron Weiss
f86f0a48ff Merge branch 'upstream' into merge 2024-08-30 12:29:34 -07:00
Aaron Weiss
78ae885a51 Merge branch 'master' into merge 2024-08-30 12:29:31 -07:00
Aaron Weiss
a74031bae7 Sync release to upstream/release/641 2024-08-30 12:28:44 -07:00
Vyacheslav Egorov
25db8ff2cb Success, DCR mode no longer fails any tests 2024-08-23 18:00:22 +03:00
Vyacheslav Egorov
5b9344a480 Missing sync elements 2024-08-23 17:58:14 +03:00
Vyacheslav Egorov
d414b56acb Merge branch 'upstream' into merge 2024-08-23 17:28:44 +03:00
Vyacheslav Egorov
caeba5a742 Merge branch 'master' into merge 2024-08-23 17:28:36 +03:00
Vyacheslav Egorov
ebdbcb1942 Sync to upstream/release/640 2024-08-23 17:24:33 +03:00
Vighnesh
3613ee9ddc Merge branch 'upstream' into merge 2024-08-16 10:15:02 -07:00
Vighnesh
8c4f9070c5 Merge branch 'master' into merge 2024-08-16 10:14:44 -07:00
Vighnesh
497c3edb91 Sync to upstream/release/639 2024-08-16 09:48:02 -07:00
Andy Friesen
c229a9e598 Merge branch 'upstream' into merge 2024-08-09 09:46:45 -07:00
Andy Friesen
057bdf3556 Merge branch 'master' into merge 2024-08-09 09:46:41 -07:00
Andy Friesen
8a99f25381 Sync to upstream/release/638 2024-08-09 09:46:26 -07:00
Junseo Yoo
1adbd45b20 Merge branch 'upstream' into merge 2024-08-01 16:26:06 -07:00
Junseo Yoo
5b401524f5 Merge branch 'master' into merge 2024-08-01 16:26:00 -07:00
Junseo Yoo
fad8aaf0ab Sync to upstream/release/637 2024-08-01 16:25:12 -07:00
Junseo Yoo
28c48cf460 Skip StudioReportLuauAny flag in tests 2024-07-26 10:46:07 -07:00
Junseo Yoo
bb7d5cf6c8 StudioReportLuauAny added to ExperimentalFlags 2024-07-26 10:22:34 -07:00
Junseo Yoo
48c0e9f625 Merge branch 'upstream' into merge 2024-07-25 17:24:36 -07:00
Junseo Yoo
fab4eec97b Merge branch 'master' into merge 2024-07-25 17:11:00 -07:00
Junseo Yoo
7dd10b16dc Sync to upstream/release/636 2024-07-25 17:10:42 -07:00
Vighnesh
ac97a491c1 Merge branch 'upstream' into merge 2024-07-19 10:40:51 -07:00
Vighnesh
79966e94a7 Merge branch 'master' into merge 2024-07-19 10:24:28 -07:00
Vighnesh
6fd26c55ff Sync to upstream/release/635 2024-07-19 10:21:40 -07:00
Junseo Yoo
5a23350108 Merge branch 'upstream' into merge 2024-07-11 16:15:04 -07:00
Junseo Yoo
a62f6619c2 Merge branch 'master' into merge 2024-07-11 15:18:21 -07:00
Junseo Yoo
1c1476fa2d Sync to upstream/release/634 2024-07-11 15:13:45 -07:00
Vighnesh
bc1bead287 Merge branch 'upstream' into merge 2024-07-08 13:28:05 -07:00
Vighnesh
2010be1575 Merge branch 'master' into merge 2024-07-08 13:27:58 -07:00
Vighnesh
cfcb545b39 Sync to upstream/release/633 2024-07-08 13:22:11 -07:00
Aaron Weiss
da48758d04 Merge branch 'upstream' into merge 2024-06-28 17:08:27 -07:00
Aaron Weiss
c5a2a4b68f Merge branch 'master' into merge 2024-06-28 17:08:21 -07:00
Aaron Weiss
240a9d8f7f Sync to upstream/release/632 2024-06-28 17:07:35 -07:00
Vyacheslav Egorov
dafb44dd3d Merge branch 'upstream' into merge 2024-06-21 01:24:20 +03:00
Vyacheslav Egorov
5d2e3de4b0 Merge branch 'master' into merge 2024-06-21 01:24:15 +03:00
Vyacheslav Egorov
816cb1d1c4 Sync to upstream/release/631 2024-06-21 01:23:57 +03:00
Vighnesh
1ba3e5fd7b correct the flag name 2024-06-14 11:23:44 -07:00
Vighnesh
2a1359dad0 Merge branch 'upstream' into merge 2024-06-14 09:40:49 -07:00
Vighnesh
91790efb8d Merge branch 'master' into merge 2024-06-14 09:40:20 -07:00
Vighnesh
58b98097c5 Sync to upstream/release/630 2024-06-14 09:38:56 -07:00
Andy Friesen
40e03164f7 Merge branch 'upstream' into merge 2024-06-07 10:20:14 -07:00
Andy Friesen
eae092a45a Merge branch 'master' into merge 2024-06-07 10:18:31 -07:00
Andy Friesen
5dd97352c1 Sync to upstream/release/629 2024-06-07 10:09:03 -07:00
Alexander McCord
93a89dcfa1 Merge branch 'heads/upstream' into merge 2024-05-31 10:47:04 -07:00
Alexander McCord
09e46d1980 Merge branch 'master' into merge 2024-05-31 10:46:57 -07:00
Alexander McCord
fede4d6393 Sync to upstream/release/628 2024-05-31 10:46:33 -07:00
Aaron Weiss
241fcf8eba Merge branch 'upstream' into merge 2024-05-26 08:34:47 -07:00
Aaron Weiss
26fb155507 Merge branch 'master' into merge 2024-05-26 08:34:43 -07:00
Aaron Weiss
bad9e1476e 627 2024-05-26 08:33:40 -07:00
Vyacheslav Egorov
0f61e4e7a4 Merge fix 2024-05-16 15:25:15 -07:00
Vyacheslav Egorov
ca46dd6fe8 Merge branch 'upstream' into merge 2024-05-16 15:22:49 -07:00
Vyacheslav Egorov
e5de2ed3cc Merge branch 'master' into merge 2024-05-16 15:22:42 -07:00
Vyacheslav Egorov
0386eec734 Sync to upstream/release/626 2024-05-16 15:22:22 -07:00
Vighnesh
f172471b87 Merge branch 'upstream' into merge 2024-05-10 09:21:07 -07:00
Vighnesh
f76a99b800 Merge branch 'master' into merge 2024-05-10 09:18:10 -07:00
Vighnesh
9bce20cb5c Sync to upstream/release/625 2024-05-10 09:17:09 -07:00
Andy Friesen
1ad7b9cc56 Merge branch 'upstream' into merge 2024-05-03 09:49:16 -07:00
Andy Friesen
f4ecf437ba Merge branch 'master' into merge 2024-05-03 09:47:22 -07:00
Andy Friesen
93468ca88d Sync to upstream/release/624 2024-05-03 09:38:34 -07:00
Alexander McCord
88dd289100 Fix missing include. 2024-04-25 14:03:47 -07:00
Alexander McCord
76ed1a5370 Merge branch 'heads/upstream' into merge 2024-04-25 13:57:53 -07:00
Alexander McCord
8d0a650a25 Merge branch 'master' into merge 2024-04-25 13:57:44 -07:00
Alexander McCord
50a2f8daa8 Sync to upstream/release/623 2024-04-25 13:57:23 -07:00
Aaron Weiss
641e9f6eb5 Merge branch 'upstream' into merge 2024-04-19 14:05:22 -07:00
Aaron Weiss
5cf508a73b Merge branch 'master' into merge 2024-04-19 14:05:01 -07:00
Aaron Weiss
67b9145268 Sync to upstream/release/622 2024-04-19 14:04:30 -07:00
Vyacheslav Egorov
858b93a5f3 Sync fixup 2024-04-12 14:07:34 +03:00
Vyacheslav Egorov
f97e96dc29 Merge branch 'upstream' into merge 2024-04-12 13:46:23 +03:00
Vyacheslav Egorov
7c346a0a69 Merge branch 'master' into merge 2024-04-12 13:45:09 +03:00
Vyacheslav Egorov
0f0c0e4d28 Sync to upstream/release/621 2024-04-12 13:44:40 +03:00
Vighnesh
5aa6d99340 transplant game engine fix for memory safety issues in normalization into OSS changes 2024-04-05 13:27:37 -07:00
Vighnesh
0f1973954c remove trailing . 2024-04-05 11:30:53 -07:00
Vighnesh
9cb93a98bc manually fix cmake configuration error 2024-04-05 11:27:48 -07:00
Vighnesh
2e1c0404d1 Merge branch 'upstream' into merge 2024-04-05 10:45:02 -07:00
Vighnesh
6bef0b10ca Merge branch 'master' into merge 2024-04-05 10:44:49 -07:00
Vighnesh
c730a51ca8 Sync to upstream/release/620 2024-04-05 10:41:05 -07:00
Lily Brown
3e1b4130ea Merge branch 'upstream' into merge 2024-03-30 15:51:08 -07:00
Lily Brown
77598ed0a6 Merge branch 'master' into merge 2024-03-30 15:50:29 -07:00
Lily Brown
fb90dc083b Sync with upstream/release/620 2024-03-30 15:49:03 -07:00
Andy Friesen
d8f49d6ca2 Compiler fixes for MSVC and GCC. 2024-03-22 10:33:09 -07:00
Andy Friesen
4931165635 Merge branch 'upstream' into merge 2024-03-22 10:24:30 -07:00
Andy Friesen
a30b2aebfd Merge branch 'master' into merge 2024-03-22 10:21:47 -07:00
Andy Friesen
6fff08b621 Sync to upstream/release/618 2024-03-22 10:21:27 -07:00
Alexander McCord
c1bbf1ebec Merge branch 'heads/upstream' into merge 2024-03-15 14:15:29 -07:00
Alexander McCord
5e9a567e09 Merge branch 'master' into merge 2024-03-15 14:11:30 -07:00
Alexander McCord
f27d4f52c3 Sync to upstream/release/617 2024-03-15 14:01:00 -07:00
Aaron Weiss
1ebdfe093a Merge branch 'upstream' into merge 2024-03-08 16:05:03 -08:00
Aaron Weiss
9e1a26c9c8 Merge branch 'master' into merge 2024-03-08 15:59:15 -08:00
Aaron Weiss
27a05c0023 Sync to upstream/release/616 2024-03-08 15:57:12 -08:00
Vyacheslav Egorov
ed4ce84e55 Merge fixes 2024-03-01 16:27:22 +02:00
Vyacheslav Egorov
f36cae2109 Merge branch 'upstream' into merge 2024-03-01 16:08:36 +02:00
Vyacheslav Egorov
532fd109e7 Merge branch 'master' into merge 2024-03-01 16:04:44 +02:00
Vyacheslav Egorov
d4a266528a Sync to upstream/release/615 2024-03-01 15:58:44 +02:00
Vighnesh
0ab33af5c2 Merge branch 'upstream' into merge 2024-02-23 11:12:27 -08:00
Vighnesh
ccb5385a72 Merge branch 'master' into merge 2024-02-23 10:55:08 -08:00
Vighnesh
22686ef1b0 Sync to upstream/release/614 2024-02-23 10:40:00 -08:00
Vyacheslav Egorov
b5f2813ab4 Merge branch 'upstream' into merge 2024-02-16 03:26:02 +02:00
Vyacheslav Egorov
1778950554 Merge branch 'master' into merge 2024-02-16 03:25:56 +02:00
Vyacheslav Egorov
158d60c223 Sync to upstream/release/613 2024-02-16 03:25:31 +02:00
Andy Friesen
ffd9f32d2c Merge branch 'upstream' into merge 2024-02-09 09:35:57 -08:00
Andy Friesen
45e72ee97b Merge branch 'master' into merge 2024-02-09 09:33:24 -08:00
Andy Friesen
1a6da94547 Sync to upstream/release/612 2024-02-09 09:32:52 -08:00
Alexander McCord
5559c7fbd5 Fix the stack-use-after-scope. 2024-02-02 13:20:35 -08:00
Alexander McCord
88d2b93351 Merge branch 'heads/upstream' into merge 2024-02-02 10:28:24 -08:00
Alexander McCord
f8f0dd94f7 Merge branch 'master' into merge 2024-02-02 10:20:39 -08:00
Alexander McCord
dfa512ba36 Sync to upstream/release/611 2024-02-02 10:20:03 -08:00
Aaron Weiss
ce2665d9f1 Merge branch 'upstream' into merge 2024-01-26 18:31:35 -08:00
Aaron Weiss
e3aba9292e Merge branch 'master' into merge 2024-01-26 18:31:32 -08:00
Aaron Weiss
0edacdded4 Sync to upstream/release/610 2024-01-26 18:30:40 -08:00
Vyacheslav Egorov
2fd3da3c14 Merge branch 'upstream' into merge 2024-01-19 17:17:03 +02:00
Vyacheslav Egorov
59a29fd322 Merge branch 'master' into merge 2024-01-19 17:16:21 +02:00
Vyacheslav Egorov
064d845269 Sync to upstream/release/609 2024-01-19 17:13:08 +02:00
Vighnesh
38aa074767 resolve memory leak in VecDeque 2024-01-12 14:11:40 -08:00
Vighnesh
2f7509dedd Merge branch 'upstream' into merge 2024-01-12 11:18:51 -08:00
Vighnesh
50b4779798 Sync to upstream/release/608 2024-01-12 11:16:39 -08:00
Vighnesh
79328350ab Merge branch 'upstream' into merge 2024-01-11 22:55:44 -08:00
Vighnesh
74b0d004ec Merge branch 'master' into merge 2024-01-11 22:47:40 -08:00
Vighnesh
0d5c842434 Sync to upstream/release/608 2024-01-11 21:28:14 -08:00
Vighnesh
d4883bfb32 Merge branch 'upstream' of https://github.com/luau-lang/luau into upstream 2024-01-11 14:55:06 -08:00
Aaron Weiss
adea0f883c fix ubuntu build with import for cstddef in DenseHash 2023-12-15 13:11:25 -08:00
Aaron Weiss
d6226187b0 Merge branch 'upstream' into merge 2023-12-15 12:53:11 -08:00
Aaron Weiss
89090a16a6 Merge branch 'master' into merge 2023-12-15 12:53:07 -08:00
Aaron Weiss
f9c5cdd4fb Sync to upstream/release/607 2023-12-15 12:52:08 -08:00
Vyacheslav Egorov
139b169361 Merge fix 2023-12-08 17:58:25 +02:00
Vyacheslav Egorov
6068432285 Merge branch 'upstream' into merge 2023-12-08 17:47:11 +02:00
Vyacheslav Egorov
f5441d7030 Merge branch 'master' into merge 2023-12-08 17:46:40 +02:00
Vyacheslav Egorov
69728e87cf Sync to upstream/release/606 2023-12-08 17:42:54 +02:00
Vighnesh
c48ffc32a4 endif 2023-12-01 20:52:10 -08:00
Vighnesh
c932b8e03f remove tests for require by string 2023-12-01 20:50:35 -08:00
Vighnesh
41669c9f30 Sync to upstream/release/605 2023-12-01 20:40:43 -08:00
Vighnesh
c592f50e20 can CI handle this? 2023-12-01 20:37:13 -08:00
Vighnesh
79854143e4 resolve one last merge conflict 2023-12-01 19:11:31 -08:00
Vighnesh
e70eec0c59 Merge branch 'upstream' into merge
Co-authored-by: Alexander McCord <amccord@roblox.com>
Co-authored-by: Andy Friesen <afriesen@roblox.com>
Co-authored-by: Aviral Goel <agoel@roblox.com>
Co-authored-by: David Cope <dcope@roblox.com>
Co-authored-by: Lily Brown <lbrown@roblox.com>
Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
2023-12-01 18:56:54 -08:00
Vighnesh
98d2db7325 add stdint import 2023-12-01 18:35:40 -08:00
Vighnesh
557e77a676 VM
- Add SUBRK and DIVRK bytecode instructions
    - Enables future performance optimizations

Miscellaneous
- Small performance improvements to new non-strict mode
- Introduce more scripts for fuzzing
- Improcements to dataflow analysis
2023-12-01 18:04:44 -08:00
Andy Friesen
2d4a544709 Merge branch 'upstream' into merge 2023-11-17 10:18:37 -08:00
Andy Friesen
43201531b3 Merge branch 'master' into merge 2023-11-17 10:18:24 -08:00
Andy Friesen
674c6c40c0 Sync to upstream/release/604 2023-11-17 10:15:31 -08:00
Alexander McCord
e57cbf6132 Merge branch 'heads/upstream' into merge 2023-11-10 10:13:36 -08:00
Alexander McCord
63436480a0 Merge branch 'master' into merge 2023-11-10 10:08:30 -08:00
Alexander McCord
4b68791b2c Sync to upstream/release/603 2023-11-10 10:05:48 -08:00
Aaron Weiss
efe133abe9 Merge branch 'upstream' into merge 2023-11-03 12:48:56 -07:00
Aaron Weiss
a0b9950541 Sync to upstream/release/602 2023-11-03 12:47:28 -07:00
521 changed files with 44334 additions and 13160 deletions

View file

@ -46,9 +46,9 @@ jobs:
- name: make cli
run: |
make -j2 config=sanitize werror=1 luau luau-analyze luau-compile # match config with tests to improve build time
./luau tests/conformance/assert.lua
./luau-analyze tests/conformance/assert.lua
./luau-compile tests/conformance/assert.lua
./luau tests/conformance/assert.luau
./luau-analyze tests/conformance/assert.luau
./luau-compile tests/conformance/assert.luau
windows:
runs-on: windows-latest
@ -81,12 +81,12 @@ jobs:
shell: bash # necessary for fail-fast
run: |
cmake --build . --target Luau.Repl.CLI Luau.Analyze.CLI Luau.Compile.CLI --config Debug # match config with tests to improve build time
Debug/luau tests/conformance/assert.lua
Debug/luau-analyze tests/conformance/assert.lua
Debug/luau-compile tests/conformance/assert.lua
Debug/luau tests/conformance/assert.luau
Debug/luau-analyze tests/conformance/assert.luau
Debug/luau-compile tests/conformance/assert.luau
coverage:
runs-on: ubuntu-20.04 # needed for clang++-10 to avoid gcov compatibility issues
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v2
- name: install
@ -94,7 +94,7 @@ jobs:
sudo apt install llvm
- name: make coverage
run: |
CXX=clang++-10 make -j2 config=coverage native=1 coverage
CXX=clang++ make -j2 config=coverage native=1 coverage
- name: upload coverage
uses: codecov/codecov-action@v3
with:

View file

@ -29,8 +29,8 @@ jobs:
build:
needs: ["create-release"]
strategy:
matrix: # using ubuntu-20.04 to build a Linux binary targeting older glibc to improve compatibility
os: [{name: ubuntu, version: ubuntu-20.04}, {name: macos, version: macos-latest}, {name: windows, version: windows-latest}]
matrix: # not using ubuntu-latest to improve compatibility
os: [{name: ubuntu, version: ubuntu-22.04}, {name: macos, version: macos-latest}, {name: windows, version: windows-latest}]
name: ${{matrix.os.name}}
runs-on: ${{matrix.os.version}}
steps:

View file

@ -13,8 +13,8 @@ on:
jobs:
build:
strategy:
matrix: # using ubuntu-20.04 to build a Linux binary targeting older glibc to improve compatibility
os: [{name: ubuntu, version: ubuntu-20.04}, {name: macos, version: macos-latest}, {name: windows, version: windows-latest}]
matrix: # not using ubuntu-latest to improve compatibility
os: [{name: ubuntu, version: ubuntu-22.04}, {name: macos, version: macos-latest}, {name: windows, version: windows-latest}]
name: ${{matrix.os.name}}
runs-on: ${{matrix.os.version}}
steps:

1
.gitignore vendored
View file

@ -13,6 +13,7 @@
/luau
/luau-tests
/luau-analyze
/luau-bytecode
/luau-compile
__pycache__
.cache

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

@ -1,10 +1,10 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "Luau/AutocompleteTypes.h"
#include "Luau/Location.h"
#include "Luau/Type.h"
#include <unordered_map>
#include <string>
#include <memory>
#include <optional>
@ -16,90 +16,8 @@ struct Frontend;
struct SourceModule;
struct Module;
struct TypeChecker;
using ModulePtr = std::shared_ptr<Module>;
enum class AutocompleteContext
{
Unknown,
Expression,
Statement,
Property,
Type,
Keyword,
String,
};
enum class AutocompleteEntryKind
{
Property,
Binding,
Keyword,
String,
Type,
Module,
GeneratedFunction,
RequirePath,
};
enum class ParenthesesRecommendation
{
None,
CursorAfter,
CursorInside,
};
enum class TypeCorrectKind
{
None,
Correct,
CorrectFunctionResult,
};
struct AutocompleteEntry
{
AutocompleteEntryKind kind = AutocompleteEntryKind::Property;
// Nullopt if kind is Keyword
std::optional<TypeId> type = std::nullopt;
bool deprecated = false;
// Only meaningful if kind is Property.
bool wrongIndexType = false;
// Set if this suggestion matches the type expected in the context
TypeCorrectKind typeCorrect = TypeCorrectKind::None;
std::optional<const ClassType*> containingClass = std::nullopt;
std::optional<const Property*> prop = std::nullopt;
std::optional<std::string> documentationSymbol = std::nullopt;
Tags tags;
ParenthesesRecommendation parens = ParenthesesRecommendation::None;
std::optional<std::string> insertText;
// Only meaningful if kind is Property.
bool indexedWithSelf = false;
};
using AutocompleteEntryMap = std::unordered_map<std::string, AutocompleteEntry>;
struct AutocompleteResult
{
AutocompleteEntryMap entryMap;
std::vector<AstNode*> ancestry;
AutocompleteContext context = AutocompleteContext::Unknown;
AutocompleteResult() = default;
AutocompleteResult(AutocompleteEntryMap entryMap, std::vector<AstNode*> ancestry, AutocompleteContext context)
: entryMap(std::move(entryMap))
, ancestry(std::move(ancestry))
, context(context)
{
}
};
using ModuleName = std::string;
using StringCompletionCallback =
std::function<std::optional<AutocompleteEntryMap>(std::string tag, std::optional<const ClassType*> ctx, std::optional<std::string> contents)>;
struct FileResolver;
AutocompleteResult autocomplete(Frontend& frontend, const ModuleName& moduleName, Position position, StringCompletionCallback callback);
constexpr char kGeneratedAnonymousFunctionEntryName[] = "function (anonymous autofilled)";
} // namespace Luau

View file

@ -0,0 +1,92 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "Luau/Ast.h"
#include "Luau/Type.h"
#include <unordered_map>
namespace Luau
{
enum class AutocompleteContext
{
Unknown,
Expression,
Statement,
Property,
Type,
Keyword,
String,
};
enum class AutocompleteEntryKind
{
Property,
Binding,
Keyword,
String,
Type,
Module,
GeneratedFunction,
RequirePath,
};
enum class ParenthesesRecommendation
{
None,
CursorAfter,
CursorInside,
};
enum class TypeCorrectKind
{
None,
Correct,
CorrectFunctionResult,
};
struct AutocompleteEntry
{
AutocompleteEntryKind kind = AutocompleteEntryKind::Property;
// Nullopt if kind is Keyword
std::optional<TypeId> type = std::nullopt;
bool deprecated = false;
// Only meaningful if kind is Property.
bool wrongIndexType = false;
// Set if this suggestion matches the type expected in the context
TypeCorrectKind typeCorrect = TypeCorrectKind::None;
std::optional<const ExternType*> containingExternType = std::nullopt;
std::optional<const Property*> prop = std::nullopt;
std::optional<std::string> documentationSymbol = std::nullopt;
Tags tags;
ParenthesesRecommendation parens = ParenthesesRecommendation::None;
std::optional<std::string> insertText;
// Only meaningful if kind is Property.
bool indexedWithSelf = false;
};
using AutocompleteEntryMap = std::unordered_map<std::string, AutocompleteEntry>;
struct AutocompleteResult
{
AutocompleteEntryMap entryMap;
std::vector<AstNode*> ancestry;
AutocompleteContext context = AutocompleteContext::Unknown;
AutocompleteResult() = default;
AutocompleteResult(AutocompleteEntryMap entryMap, std::vector<AstNode*> ancestry, AutocompleteContext context)
: entryMap(std::move(entryMap))
, ancestry(std::move(ancestry))
, context(context)
{
}
};
using StringCompletionCallback =
std::function<std::optional<AutocompleteEntryMap>(std::string tag, std::optional<const ExternType*> ctx, std::optional<std::string> contents)>;
constexpr char kGeneratedAnonymousFunctionEntryName[] = "function (anonymous autofilled)";
} // namespace Luau

View file

@ -65,14 +65,12 @@ TypeId makeFunction( // Polymorphic
bool checked = false
);
void attachMagicFunction(TypeId ty, MagicFunction fn);
void attachDcrMagicFunction(TypeId ty, DcrMagicFunction fn);
void attachDcrMagicRefinement(TypeId ty, DcrMagicRefinement fn);
void attachDcrMagicFunctionTypeCheck(TypeId ty, DcrMagicFunctionTypeCheck fn);
void attachMagicFunction(TypeId ty, std::shared_ptr<MagicFunction> fn);
Property makeProperty(TypeId ty, std::optional<std::string> documentationSymbol = std::nullopt);
void assignPropDocumentationSymbols(TableType::Props& props, const std::string& baseName);
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, Binding binding);

View file

@ -4,6 +4,7 @@
#include <Luau/NotNull.h>
#include "Luau/TypeArena.h"
#include "Luau/Type.h"
#include "Luau/Scope.h"
#include <unordered_map>
@ -26,13 +27,22 @@ struct CloneState
* while `clone` will make a deep copy of the entire type and its every component.
*
* Be mindful about which behavior you actually _want_.
*
* Persistent types are not cloned as an optimization.
* If a type is cloned in order to mutate it, 'ignorePersistent' has to be set
*/
TypePackId shallowClone(TypePackId tp, TypeArena& dest, CloneState& cloneState);
TypeId shallowClone(TypeId typeId, TypeArena& dest, CloneState& cloneState);
TypePackId shallowClone(TypePackId tp, TypeArena& dest, CloneState& cloneState, bool ignorePersistent = false);
TypeId shallowClone(TypeId typeId, TypeArena& dest, CloneState& cloneState, bool ignorePersistent = false);
TypePackId clone(TypePackId tp, TypeArena& dest, CloneState& cloneState);
TypeId clone(TypeId tp, TypeArena& dest, CloneState& cloneState);
TypeFun clone(const TypeFun& typeFun, TypeArena& dest, CloneState& cloneState);
Binding clone(const Binding& binding, TypeArena& dest, CloneState& cloneState);
TypePackId cloneIncremental(TypePackId tp, TypeArena& dest, CloneState& cloneState, Scope* freshScopeForFreeTypes);
TypeId cloneIncremental(TypeId typeId, TypeArena& dest, CloneState& cloneState, Scope* freshScopeForFreeTypes);
TypeFun cloneIncremental(const TypeFun& typeFun, TypeArena& dest, CloneState& cloneState, Scope* freshScopeForFreeTypes);
Binding cloneIncremental(const Binding& binding, TypeArena& dest, CloneState& cloneState, Scope* freshScopeForFreeTypes);
} // namespace Luau

View file

@ -50,6 +50,7 @@ struct GeneralizationConstraint
TypeId sourceType;
std::vector<TypeId> interiorTypes;
bool hasDeprecatedAttribute = false;
};
// variables ~ iterate iterator
@ -109,6 +110,21 @@ struct FunctionCheckConstraint
NotNull<DenseHashMap<const AstExpr*, TypeId>> astExpectedTypes;
};
// table_check expectedType exprType
//
// If `expectedType` is a table type and `exprType` is _also_ a table type,
// propogate the member types of `expectedType` into the types of `exprType`.
// This is used to implement bidirectional inference on table assignment.
// Also see: FunctionCheckConstraint.
struct TableCheckConstraint
{
TypeId expectedType;
TypeId exprType;
AstExprTable* table = nullptr;
NotNull<DenseHashMap<const AstExpr*, TypeId>> astTypes;
NotNull<DenseHashMap<const AstExpr*, TypeId>> astExpectedTypes;
};
// prim FreeType ExpectedType PrimitiveType
//
// FreeType is bounded below by the singleton type and above by PrimitiveType
@ -273,7 +289,8 @@ using ConstraintV = Variant<
UnpackConstraint,
ReduceConstraint,
ReducePackConstraint,
EqualityConstraint>;
EqualityConstraint,
TableCheckConstraint>;
struct Constraint
{

View file

@ -3,23 +3,23 @@
#include "Luau/Ast.h"
#include "Luau/Constraint.h"
#include "Luau/ConstraintSet.h"
#include "Luau/ControlFlow.h"
#include "Luau/DataFlowGraph.h"
#include "Luau/EqSatSimplification.h"
#include "Luau/InsertionOrderedMap.h"
#include "Luau/Module.h"
#include "Luau/ModuleResolver.h"
#include "Luau/Normalize.h"
#include "Luau/NotNull.h"
#include "Luau/Polarity.h"
#include "Luau/Refinement.h"
#include "Luau/Symbol.h"
#include "Luau/TypeFwd.h"
#include "Luau/TypeUtils.h"
#include "Luau/Variant.h"
#include "Luau/Normalize.h"
#include <memory>
#include <vector>
#include <unordered_map>
namespace Luau
{
@ -92,9 +92,11 @@ struct ConstraintGenerator
// Constraints that go straight to the solver.
std::vector<ConstraintPtr> constraints;
// Constraints that do not go to the solver right away. Other constraints
// will enqueue them during solving.
std::vector<ConstraintPtr> unqueuedConstraints;
// The set of all free types introduced during constraint generation.
DenseHashSet<TypeId> freeTypes{nullptr};
// Map a function's signature scope back to its signature type.
DenseHashMap<Scope*, TypeId> scopeToFunction{nullptr};
// The private scope of type aliases for which the type parameters belong to.
DenseHashMap<const AstStatTypeAlias*, ScopePtr> astTypeAliasDefiningScopes{nullptr};
@ -109,36 +111,49 @@ struct ConstraintGenerator
// Needed to be able to enable error-suppression preservation for immediate refinements.
NotNull<Normalizer> normalizer;
NotNull<Simplifier> simplifier;
// Needed to register all available type functions for execution at later stages.
NotNull<TypeFunctionRuntime> typeFunctionRuntime;
DenseHashMap<const AstStatTypeFunction*, ScopePtr> astTypeFunctionEnvironmentScopes{nullptr};
// Needed to resolve modules to make 'require' import types properly.
NotNull<ModuleResolver> moduleResolver;
// Occasionally constraint generation needs to produce an ICE.
const NotNull<InternalErrorReporter> ice;
ScopePtr globalScope;
ScopePtr typeFunctionScope;
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope;
std::vector<RequireCycle> requireCycles;
DenseHashMap<TypeId, TypeIds> localTypes{nullptr};
DenseHashMap<AstExpr*, Inference> inferredExprCache{nullptr};
DcrLogger* logger;
ConstraintGenerator(
ModulePtr module,
NotNull<Normalizer> normalizer,
NotNull<Simplifier> simplifier,
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
NotNull<ModuleResolver> moduleResolver,
NotNull<BuiltinTypes> builtinTypes,
NotNull<InternalErrorReporter> ice,
const ScopePtr& globalScope,
const ScopePtr& typeFunctionScope,
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope,
DcrLogger* logger,
NotNull<DataFlowGraph> dfg,
std::vector<RequireCycle> requireCycles
);
ConstraintSet run(AstStatBlock* block);
ConstraintSet runOnFragment(const ScopePtr& resumeScope, AstStatBlock* block);
/**
* The entry point to the ConstraintGenerator. This will construct a set
* of scopes, constraints, and free types that can be solved later.
@ -149,19 +164,26 @@ struct ConstraintGenerator
void visitFragmentRoot(const ScopePtr& resumeScope, AstStatBlock* block);
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.
* @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.
* @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.
@ -250,7 +272,7 @@ private:
ControlFlow visit(const ScopePtr& scope, AstStatTypeAlias* alias);
ControlFlow visit(const ScopePtr& scope, AstStatTypeFunction* function);
ControlFlow visit(const ScopePtr& scope, AstStatDeclareGlobal* declareGlobal);
ControlFlow visit(const ScopePtr& scope, AstStatDeclareClass* declareClass);
ControlFlow visit(const ScopePtr& scope, AstStatDeclareExternType* declareExternType);
ControlFlow visit(const ScopePtr& scope, AstStatDeclareFunction* declareFunction);
ControlFlow visit(const ScopePtr& scope, AstStatError* error);
@ -282,7 +304,7 @@ private:
);
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, AstExprGlobal* global);
Inference checkIndexName(const ScopePtr& scope, const RefinementKey* key, AstExpr* indexee, const std::string& index, Location indexLocation);
@ -291,11 +313,25 @@ private:
Inference check(const ScopePtr& scope, AstExprFunction* func, std::optional<TypeId> expectedType, bool generalize);
Inference check(const ScopePtr& scope, AstExprUnary* unary);
Inference check(const ScopePtr& scope, AstExprBinary* binary, std::optional<TypeId> expectedType);
Inference checkAstExprBinary(
const ScopePtr& scope,
const Location& location,
AstExprBinary::Op op,
AstExpr* left,
AstExpr* right,
std::optional<TypeId> expectedType
);
Inference check(const ScopePtr& scope, AstExprIfElse* ifElse, std::optional<TypeId> expectedType);
Inference check(const ScopePtr& scope, AstExprTypeAssertion* typeAssert);
Inference check(const ScopePtr& scope, AstExprInterpString* interpString);
Inference check(const ScopePtr& scope, AstExprTable* expr, std::optional<TypeId> expectedType);
std::tuple<TypeId, TypeId, RefinementId> checkBinary(const ScopePtr& scope, AstExprBinary* binary, std::optional<TypeId> expectedType);
std::tuple<TypeId, TypeId, RefinementId> checkBinary(
const ScopePtr& scope,
AstExprBinary::Op op,
AstExpr* left,
AstExpr* right,
std::optional<TypeId> expectedType
);
void visitLValue(const ScopePtr& scope, AstExpr* expr, TypeId rhsType);
void visitLValue(const ScopePtr& scope, AstExprLocal* local, TypeId rhsType);
@ -344,6 +380,11 @@ private:
**/
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.
* @param scope the scope that the type annotation appears within.
@ -353,6 +394,9 @@ private:
**/
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.
* @param scope the scope that the type annotation appears within.
@ -374,7 +418,7 @@ private:
**/
std::vector<std::pair<Name, GenericTypeDefinition>> createGenerics(
const ScopePtr& scope,
AstArray<AstGenericType> generics,
AstArray<AstGenericType*> generics,
bool useCache = false,
bool addTypes = true
);
@ -391,7 +435,7 @@ private:
**/
std::vector<std::pair<Name, GenericTypePackDefinition>> createGenericPacks(
const ScopePtr& scope,
AstArray<AstGenericTypePack> packs,
AstArray<AstGenericTypePack*> generics,
bool useCache = false,
bool addTypes = true
);
@ -405,6 +449,7 @@ private:
TypeId makeUnion(const ScopePtr& scope, Location location, TypeId lhs, TypeId rhs);
// make an intersect type function of these two types
TypeId makeIntersect(const ScopePtr& scope, Location location, TypeId lhs, TypeId rhs);
void prepopulateGlobalScopeForFragmentTypecheck(const ScopePtr& globalScope, const ScopePtr& resumeScope, AstStatBlock* program);
/** Scan the program for global definitions.
*
@ -435,11 +480,8 @@ private:
const ScopePtr& scope,
Location location
);
TypeId simplifyUnion(const ScopePtr& scope, Location location, TypeId left, TypeId right);
};
/** Borrow a vector of pointers from a vector of owning pointers to constraints.
*/
std::vector<NotNull<Constraint>> borrowConstraints(const std::vector<ConstraintPtr>& constraints);
} // namespace Luau

View file

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

View file

@ -3,8 +3,10 @@
#pragma once
#include "Luau/Constraint.h"
#include "Luau/ConstraintSet.h"
#include "Luau/DataFlowGraph.h"
#include "Luau/DenseHash.h"
#include "Luau/EqSatSimplification.h"
#include "Luau/Error.h"
#include "Luau/Location.h"
#include "Luau/Module.h"
@ -58,15 +60,37 @@ struct HashInstantiationSignature
size_t operator()(const InstantiationSignature& signature) const;
};
struct TablePropLookupResult
{
// What types are we blocked on for determining this type?
std::vector<TypeId> blockedTypes;
// The type of the property (if we were able to determine it).
std::optional<TypeId> propType;
// Whether or not this is _definitely_ derived as the result of an indexer.
// We use this to determine whether or not code like:
//
// t.lol = nil;
//
// ... is legal. If `t: { [string]: ~nil }` then this is legal as
// there's no guarantee on whether "lol" specifically exists.
// However, if `t: { lol: ~nil }`, then we cannot allow assignment as
// that would remove "lol" from the table entirely.
bool isIndex = false;
};
struct ConstraintSolver
{
NotNull<TypeArena> arena;
NotNull<BuiltinTypes> builtinTypes;
InternalErrorReporter iceReporter;
NotNull<Normalizer> normalizer;
NotNull<Simplifier> simplifier;
NotNull<TypeFunctionRuntime> typeFunctionRuntime;
// The entire set of constraints that the solver is trying to resolve.
ConstraintSet constraintSet;
std::vector<NotNull<Constraint>> constraints;
NotNull<DenseHashMap<Scope*, TypeId>> scopeToFunction;
NotNull<Scope> rootScope;
ModuleName currentModuleName;
@ -97,6 +121,9 @@ struct ConstraintSolver
// A mapping from free types to the number of unresolved constraints that mention them.
DenseHashMap<TypeId, size_t> unresolvedConstraints{{}};
std::unordered_map<NotNull<const Constraint>, DenseHashSet<TypeId>> maybeMutatedFreeTypes;
std::unordered_map<TypeId, DenseHashSet<const Constraint*>> mutatedFreeTypeToConstraint;
// Irreducible/uninhabited type functions or type pack functions.
DenseHashSet<const void*> uninhabitedTypeFunctions{{}};
@ -117,9 +144,24 @@ struct ConstraintSolver
explicit ConstraintSolver(
NotNull<Normalizer> normalizer,
NotNull<Simplifier> simplifier,
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
ModuleName moduleName,
NotNull<ModuleResolver> moduleResolver,
std::vector<RequireCycle> requireCycles,
DcrLogger* logger,
NotNull<const DataFlowGraph> dfg,
TypeCheckLimits limits,
ConstraintSet constraintSet
);
explicit ConstraintSolver(
NotNull<Normalizer> normalizer,
NotNull<Simplifier> simplifier,
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
NotNull<Scope> rootScope,
std::vector<NotNull<Constraint>> constraints,
NotNull<DenseHashMap<Scope*, TypeId>> scopeToFunction,
ModuleName moduleName,
NotNull<ModuleResolver> moduleResolver,
std::vector<RequireCycle> requireCycles,
@ -144,9 +186,14 @@ struct ConstraintSolver
**/
void finalizeTypeFunctions();
bool isDone();
bool isDone() const;
private:
/// A helper that does most of the setup work that is shared between the two constructors.
void initFreeTypeTracking();
void generalizeOneType(TypeId ty);
/**
* Bind a type variable to another type.
*
@ -179,6 +226,7 @@ public:
bool tryDispatch(const NameConstraint& c, NotNull<const Constraint> constraint);
bool tryDispatch(const TypeAliasExpansionConstraint& c, NotNull<const Constraint> constraint);
bool tryDispatch(const FunctionCallConstraint& c, NotNull<const Constraint> constraint);
bool tryDispatch(const TableCheckConstraint& c, NotNull<const Constraint> constraint);
bool tryDispatch(const FunctionCheckConstraint& c, NotNull<const Constraint> constraint);
bool tryDispatch(const PrimitiveTypeConstraint& c, NotNull<const Constraint> constraint);
bool tryDispatch(const HasPropConstraint& c, NotNull<const Constraint> constraint);
@ -208,7 +256,7 @@ public:
// for a, ... in next_function, t, ... do
bool tryDispatchIterableFunction(TypeId nextTy, TypeId tableTy, const IterableConstraint& c, NotNull<const Constraint> constraint);
std::pair<std::vector<TypeId>, std::optional<TypeId>> lookupTableProp(
TablePropLookupResult lookupTableProp(
NotNull<const Constraint> constraint,
TypeId subjectType,
const std::string& propName,
@ -216,7 +264,8 @@ public:
bool inConditional = false,
bool suppressSimplification = false
);
std::pair<std::vector<TypeId>, std::optional<TypeId>> lookupTableProp(
TablePropLookupResult lookupTableProp(
NotNull<const Constraint> constraint,
TypeId subjectType,
const std::string& propName,
@ -275,10 +324,10 @@ public:
// FIXME: This use of a boolean for the return result is an appalling
// interface.
bool blockOnPendingTypes(TypeId target, NotNull<const Constraint> constraint);
bool blockOnPendingTypes(TypePackId target, NotNull<const Constraint> constraint);
bool blockOnPendingTypes(TypePackId targetPack, NotNull<const Constraint> constraint);
void unblock(NotNull<const Constraint> progressed);
void unblock(TypeId progressed, Location location);
void unblock(TypeId ty, Location location);
void unblock(TypePackId progressed, Location location);
void unblock(const std::vector<TypeId>& types, Location location);
void unblock(const std::vector<TypePackId>& packs, Location location);
@ -313,7 +362,7 @@ public:
* @param location the location where the require is taking place; used for
* error locations.
**/
TypeId resolveModule(const ModuleInfo& module, const Location& location);
TypeId resolveModule(const ModuleInfo& info, const Location& location);
void reportError(TypeErrorData&& data, const Location& location);
void reportError(TypeError e);
@ -334,7 +383,7 @@ public:
* @returns a non-free type that generalizes the argument, or `std::nullopt` if one
* 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
@ -384,6 +433,10 @@ public:
**/
void reproduceConstraints(NotNull<Scope> scope, const Location& location, const Substitution& subst);
TypeId simplifyIntersection(NotNull<Scope> scope, Location location, TypeId left, TypeId right);
TypeId simplifyIntersection(NotNull<Scope> scope, Location location, std::set<TypeId> parts);
TypeId simplifyUnion(NotNull<Scope> scope, Location location, TypeId left, TypeId right);
TypeId errorRecoveryType() const;
TypePackId errorRecoveryTypePack() const;
@ -393,8 +446,14 @@ public:
void throwUserCancelError() const;
ToStringOptions opts;
void fillInDiscriminantTypes(NotNull<const Constraint> constraint, const std::vector<std::optional<TypeId>>& discriminantTypes);
};
/** Borrow a vector of pointers from a vector of owning pointers to constraints.
*/
std::vector<NotNull<Constraint>> borrowConstraints(const std::vector<ConstraintPtr>& constraints);
void dump(NotNull<Scope> rootScope, struct ToStringOptions& opts);
} // namespace Luau

View file

@ -6,6 +6,7 @@
#include "Luau/ControlFlow.h"
#include "Luau/DenseHash.h"
#include "Luau/Def.h"
#include "Luau/NotNull.h"
#include "Luau/Symbol.h"
#include "Luau/TypedAllocator.h"
@ -37,8 +38,6 @@ struct DataFlowGraph
DefId getDef(const AstExpr* expr) const;
// Look up the definition optionally, knowing it may not be present.
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;
@ -48,13 +47,13 @@ struct DataFlowGraph
const RefinementKey* getRefinementKey(const AstExpr* expr) const;
private:
DataFlowGraph() = default;
DataFlowGraph(NotNull<DefArena> defArena, NotNull<RefinementKeyArena> keyArena);
DataFlowGraph(const DataFlowGraph&) = delete;
DataFlowGraph& operator=(const DataFlowGraph&) = delete;
DefArena defArena;
RefinementKeyArena keyArena;
NotNull<DefArena> defArena;
NotNull<RefinementKeyArena> keyArena;
DenseHashMap<const AstExpr*, const Def*> astDefs{nullptr};
@ -65,10 +64,6 @@ private:
// All keys in this maps are really only statements that ambiently declares a symbol.
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};
friend struct DataFlowGraphBuilder;
};
@ -84,7 +79,6 @@ struct DfgScope
DfgScope* parent;
ScopeType scopeType;
Location location;
using Bindings = DenseHashMap<Symbol, const Def*>;
using Props = DenseHashMap<const Def*, std::unordered_map<std::string, const Def*>>;
@ -111,49 +105,22 @@ using ScopeStack = std::vector<DfgScope*>;
struct DataFlowGraphBuilder
{
static DataFlowGraph build(AstStatBlock* root, NotNull<struct InternalErrorReporter> handle);
/**
* This method is identical to the build method above, but returns a pair of dfg, scopes as the data flow graph
* here is intended to live on the module between runs of typechecking. Before, the DFG only needed to live as
* long as the typecheck, but in a world with incremental typechecking, we need the information on the dfg to incrementally
* typecheck small fragments of code.
* @param block - pointer to the ast to build the dfg for
* @param handle - for raising internal errors while building the dfg
*/
static std::pair<std::shared_ptr<DataFlowGraph>, std::vector<std::unique_ptr<DfgScope>>> buildShared(
static DataFlowGraph build(
AstStatBlock* block,
NotNull<InternalErrorReporter> handle
);
/**
* Takes a stale graph along with a list of scopes, a small fragment of the ast, and a cursor position
* and constructs the DataFlowGraph for just that fragment. This method will fabricate defs in the final
* DFG for things that have been referenced and exist in the stale dfg.
* For example, the fragment local z = x + y will populate defs for x and y from the stale graph.
* @param staleGraph - the old DFG
* @param scopes - the old DfgScopes in the graph
* @param fragment - the Ast Fragment to re-build the root for
* @param cursorPos - the current location of the cursor - used to determine which scope we are currently in
* @param handle - for internal compiler errors
*/
static DataFlowGraph updateGraph(
const DataFlowGraph& staleGraph,
const std::vector<std::unique_ptr<DfgScope>>& scopes,
AstStatBlock* fragment,
const Position& cursorPos,
NotNull<InternalErrorReporter> handle
NotNull<DefArena> defArena,
NotNull<RefinementKeyArena> keyArena,
NotNull<struct InternalErrorReporter> handle
);
private:
DataFlowGraphBuilder() = default;
DataFlowGraphBuilder(NotNull<DefArena> defArena, NotNull<RefinementKeyArena> keyArena);
DataFlowGraphBuilder(const DataFlowGraphBuilder&) = delete;
DataFlowGraphBuilder& operator=(const DataFlowGraphBuilder&) = delete;
DataFlowGraph graph;
NotNull<DefArena> defArena{&graph.defArena};
NotNull<RefinementKeyArena> keyArena{&graph.keyArena};
NotNull<DefArena> defArena;
NotNull<RefinementKeyArena> keyArena;
struct InternalErrorReporter* handle = nullptr;
@ -162,8 +129,8 @@ private:
/// A stack of scopes used by the visitor to see where we are.
ScopeStack scopeStack;
DfgScope* currentScope();
NotNull<DfgScope> currentScope();
DfgScope* currentScope_DEPRECATED();
struct FunctionCapture
{
@ -175,14 +142,14 @@ private:
DenseHashMap<Symbol, FunctionCapture> captures{Symbol{}};
void resolveCaptures();
DfgScope* makeChildScope(Location loc, DfgScope::ScopeType scopeType = DfgScope::Linear);
DfgScope* makeChildScope(DfgScope::ScopeType scopeType = DfgScope::Linear);
void join(DfgScope* p, DfgScope* a, DfgScope* b);
void joinBindings(DfgScope* p, const DfgScope& a, const DfgScope& b);
void joinProps(DfgScope* p, const DfgScope& a, const DfgScope& b);
DefId lookup(Symbol symbol);
DefId lookup(DefId def, const std::string& key);
DefId lookup(Symbol symbol, Location location);
DefId lookup(DefId def, const std::string& key, Location location);
ControlFlow visit(AstStatBlock* b);
ControlFlow visitBlockWithoutChildScope(AstStatBlock* b);
@ -206,7 +173,7 @@ private:
ControlFlow visit(AstStatTypeFunction* f);
ControlFlow visit(AstStatDeclareGlobal* d);
ControlFlow visit(AstStatDeclareFunction* d);
ControlFlow visit(AstStatDeclareClass* d);
ControlFlow visit(AstStatDeclareExternType* d);
ControlFlow visit(AstStatError* error);
DataFlowResult visitExpr(AstExpr* e);
@ -248,8 +215,8 @@ private:
void visitTypeList(AstTypeList l);
void visitGenerics(AstArray<AstGenericType> g);
void visitGenericPacks(AstArray<AstGenericTypePack> g);
void visitGenerics(AstArray<AstGenericType*> g);
void visitGenericPacks(AstArray<AstGenericTypePack*> g);
};
} // namespace Luau

View file

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

View file

@ -0,0 +1,50 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "Luau/TypeFwd.h"
#include "Luau/NotNull.h"
#include "Luau/DenseHash.h"
#include <memory>
#include <optional>
#include <vector>
namespace Luau
{
struct TypeArena;
}
// The EqSat stuff is pretty template heavy, so we go to some lengths to prevent
// the complexity from leaking outside its implementation sources.
namespace Luau::EqSatSimplification
{
struct Simplifier;
using SimplifierPtr = std::unique_ptr<Simplifier, void (*)(Simplifier*)>;
SimplifierPtr newSimplifier(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes);
} // namespace Luau::EqSatSimplification
namespace Luau
{
struct EqSatSimplificationResult
{
TypeId result;
// New type function applications that were created by the reduction phase.
// We return these so that the ConstraintSolver can know to try to reduce
// them.
std::vector<TypeId> newTypeFunctions;
};
using EqSatSimplification::newSimplifier; // NOLINT: clang-tidy thinks these are unused. It is incorrect.
using Luau::EqSatSimplification::Simplifier; // NOLINT
using Luau::EqSatSimplification::SimplifierPtr;
std::optional<EqSatSimplificationResult> eqSatSimplify(NotNull<Simplifier> simplifier, TypeId ty);
} // namespace Luau

View file

@ -0,0 +1,376 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "Luau/EGraph.h"
#include "Luau/Id.h"
#include "Luau/Language.h"
#include "Luau/Lexer.h" // For Allocator
#include "Luau/NotNull.h"
#include "Luau/TypeArena.h"
#include "Luau/TypeFwd.h"
namespace Luau
{
struct TypeFunction;
}
namespace Luau::EqSatSimplification
{
using StringId = uint32_t;
using Id = Luau::EqSat::Id;
LUAU_EQSAT_UNIT(TNil);
LUAU_EQSAT_UNIT(TBoolean);
LUAU_EQSAT_UNIT(TNumber);
LUAU_EQSAT_UNIT(TString);
LUAU_EQSAT_UNIT(TThread);
LUAU_EQSAT_UNIT(TTopFunction);
LUAU_EQSAT_UNIT(TTopTable);
LUAU_EQSAT_UNIT(TTopClass);
LUAU_EQSAT_UNIT(TBuffer);
// Used for any type that eqsat can't do anything interesting with.
LUAU_EQSAT_ATOM(TOpaque, TypeId);
LUAU_EQSAT_ATOM(SBoolean, bool);
LUAU_EQSAT_ATOM(SString, StringId);
LUAU_EQSAT_ATOM(TFunction, TypeId);
LUAU_EQSAT_ATOM(TImportedTable, TypeId);
LUAU_EQSAT_ATOM(TClass, TypeId);
LUAU_EQSAT_UNIT(TAny);
LUAU_EQSAT_UNIT(TError);
LUAU_EQSAT_UNIT(TUnknown);
LUAU_EQSAT_UNIT(TNever);
LUAU_EQSAT_NODE_SET(Union);
LUAU_EQSAT_NODE_SET(Intersection);
LUAU_EQSAT_NODE_ARRAY(Negation, 1);
LUAU_EQSAT_NODE_ATOM_WITH_VECTOR(TTypeFun, std::shared_ptr<const TypeFunctionInstanceType>);
LUAU_EQSAT_UNIT(TNoRefine);
LUAU_EQSAT_UNIT(Invalid);
// enodes are immutable, but types are cyclic. We need a way to tie the knot.
// We handle this by generating TBound nodes at points where we encounter cycles.
// Each TBound has an ordinal that we later map onto the type.
// We use a substitution rule to replace all TBound nodes with their referrent.
LUAU_EQSAT_ATOM(TBound, size_t);
// Tables are sufficiently unlike other enodes that the Language.h macros won't cut it.
struct TTable
{
explicit TTable(Id basis);
TTable(Id basis, std::vector<StringId> propNames_, std::vector<Id> propTypes_);
// All TTables extend some other table. This may be TTopTable.
//
// It will frequently be a TImportedTable, in which case we can reuse things
// like source location and documentation info.
Id getBasis() const;
EqSat::Slice<const Id> propTypes() const;
// TODO: Also support read-only table props
// TODO: Indexer type, index result type.
std::vector<StringId> propNames;
// The enode interface
EqSat::Slice<Id> mutableOperands();
EqSat::Slice<const Id> operands() const;
bool operator==(const TTable& rhs) const;
bool operator!=(const TTable& rhs) const
{
return !(*this == rhs);
}
struct Hash
{
size_t operator()(const TTable& value) const;
};
private:
// The first element of this vector is the basis. Subsequent elements are
// property types. As we add other things like read-only properties and
// indexers, the structure of this array is likely to change.
//
// We encode our data in this way so that the operands() method can properly
// return a Slice<Id>.
std::vector<Id> storage;
};
template<typename L>
using Node = EqSat::Node<L>;
using EType = EqSat::Language<
TNil,
TBoolean,
TNumber,
TString,
TThread,
TTopFunction,
TTopTable,
TTopClass,
TBuffer,
TOpaque,
SBoolean,
SString,
TFunction,
TTable,
TImportedTable,
TClass,
TAny,
TError,
TUnknown,
TNever,
Union,
Intersection,
Negation,
TTypeFun,
Invalid,
TNoRefine,
TBound>;
struct StringCache
{
Allocator allocator;
DenseHashMap<std::string_view, StringId> strings{{}};
std::vector<std::string_view> views;
StringId add(std::string_view s);
std::string_view asStringView(StringId id) const;
std::string asString(StringId id) const;
};
using EGraph = Luau::EqSat::EGraph<EType, struct Simplify>;
struct Simplify
{
using Data = bool;
template<typename T>
Data make(const EGraph&, const T&) const;
void join(Data& left, const Data& right) const;
};
struct Subst
{
Id eclass;
Id newClass;
// The node into eclass which is boring, if any
std::optional<size_t> boringIndex;
std::string desc;
Subst(Id eclass, Id newClass, std::string desc = "");
};
struct Simplifier
{
NotNull<TypeArena> arena;
NotNull<BuiltinTypes> builtinTypes;
EGraph egraph;
StringCache stringCache;
// enodes are immutable but types can be cyclic, so we need some way to
// encode the cycle. This map is used to connect TBound nodes to the right
// eclass.
//
// The cyclicIntersection rewrite rule uses this to sense when a cycle can
// be deleted from an intersection or union.
std::unordered_map<size_t, Id> mappingIdToClass;
std::vector<Subst> substs;
using RewriteRuleFn = void (Simplifier::*)(Id id);
Simplifier(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes);
// Utilities
const EqSat::EClass<EType, Simplify::Data>& get(Id id) const;
Id find(Id id) const;
Id add(EType enode);
template<typename Tag>
const Tag* isTag(Id id) const;
template<typename Tag>
const Tag* isTag(const EType& enode) const;
void subst(Id from, Id to);
void subst(Id from, Id to, const std::string& ruleName);
void subst(Id from, Id to, const std::string& ruleName, const std::unordered_map<Id, size_t>& forceNodes);
void subst(Id from, size_t boringIndex, Id to, const std::string& ruleName, const std::unordered_map<Id, size_t>& forceNodes);
void unionClasses(std::vector<Id>& hereParts, Id there);
// Rewrite rules
void simplifyUnion(Id id);
void uninhabitedIntersection(Id id);
void intersectWithNegatedClass(Id id);
void intersectWithNegatedAtom(Id id);
void intersectWithNoRefine(Id id);
void cyclicIntersectionOfUnion(Id id);
void cyclicUnionOfIntersection(Id id);
void expandNegation(Id id);
void intersectionOfUnion(Id id);
void intersectTableProperty(Id id);
void uninhabitedTable(Id id);
void unneededTableModification(Id id);
void builtinTypeFunctions(Id id);
void iffyTypeFunctions(Id id);
void strictMetamethods(Id id);
};
template<typename Tag>
struct QueryIterator
{
QueryIterator();
QueryIterator(EGraph* egraph, Id eclass);
bool operator==(const QueryIterator& other) const;
bool operator!=(const QueryIterator& other) const;
std::pair<const Tag*, size_t> operator*() const;
QueryIterator& operator++();
QueryIterator& operator++(int);
private:
EGraph* egraph = nullptr;
Id eclass;
size_t index = 0;
};
template<typename Tag>
struct Query
{
EGraph* egraph;
Id eclass;
Query(EGraph* egraph, Id eclass)
: egraph(egraph)
, eclass(eclass)
{
}
QueryIterator<Tag> begin()
{
return QueryIterator<Tag>{egraph, eclass};
}
QueryIterator<Tag> end()
{
return QueryIterator<Tag>{};
}
};
template<typename Tag>
QueryIterator<Tag>::QueryIterator()
: egraph(nullptr)
, eclass(Id{0})
, index(0)
{
}
template<typename Tag>
QueryIterator<Tag>::QueryIterator(EGraph* egraph_, Id eclass)
: egraph(egraph_)
, eclass(eclass)
, index(0)
{
const auto& ecl = (*egraph)[eclass];
static constexpr const int idx = EType::VariantTy::getTypeId<Tag>();
for (const auto& enode : ecl.nodes)
{
if (enode.node.index() < idx)
++index;
else
break;
}
if (index >= ecl.nodes.size() || ecl.nodes[index].node.index() != idx)
{
egraph = nullptr;
index = 0;
}
}
template<typename Tag>
bool QueryIterator<Tag>::operator==(const QueryIterator<Tag>& rhs) const
{
if (egraph == nullptr && rhs.egraph == nullptr)
return true;
return egraph == rhs.egraph && eclass == rhs.eclass && index == rhs.index;
}
template<typename Tag>
bool QueryIterator<Tag>::operator!=(const QueryIterator<Tag>& rhs) const
{
return !(*this == rhs);
}
template<typename Tag>
std::pair<const Tag*, size_t> QueryIterator<Tag>::operator*() const
{
LUAU_ASSERT(egraph != nullptr);
EGraph::EClassT& ecl = (*egraph)[eclass];
LUAU_ASSERT(index < ecl.nodes.size());
auto& enode = ecl.nodes[index].node;
Tag* result = enode.template get<Tag>();
LUAU_ASSERT(result);
return {result, index};
}
// pre-increment
template<typename Tag>
QueryIterator<Tag>& QueryIterator<Tag>::operator++()
{
const auto& ecl = (*egraph)[eclass];
do
{
++index;
if (index >= ecl.nodes.size() || ecl.nodes[index].node.index() != EType::VariantTy::getTypeId<Tag>())
{
egraph = nullptr;
index = 0;
break;
}
} while (ecl.nodes[index].boring);
return *this;
}
// post-increment
template<typename Tag>
QueryIterator<Tag>& QueryIterator<Tag>::operator++(int)
{
QueryIterator<Tag> res = *this;
++res;
return res;
}
} // namespace Luau::EqSatSimplification

View file

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

View file

@ -1,8 +1,9 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include <string>
#include <memory>
#include <optional>
#include <string>
#include <vector>
namespace Luau
@ -19,7 +20,7 @@ struct SourceCode
None,
Module,
Script,
Local
Local_DEPRECATED
};
std::string source;
@ -32,11 +33,71 @@ struct ModuleInfo
bool optional = false;
};
using RequireSuggestion = std::string;
struct RequireAlias
{
std::string alias; // Unprefixed alias name (no leading `@`).
std::vector<std::string> tags = {};
};
struct RequireNode
{
virtual ~RequireNode() {}
// Get the path component representing this node.
virtual std::string getPathComponent() const = 0;
// Get the displayed user-facing label for this node, defaults to getPathComponent()
virtual std::string getLabel() const
{
return getPathComponent();
}
// Get tags to attach to this node's RequireSuggestion (defaults to none).
virtual std::vector<std::string> getTags() const
{
return {};
}
// TODO: resolvePathToNode() can ultimately be replaced with a call into
// require-by-string's path resolution algorithm. This will first require
// generalizing that algorithm to work with a virtual file system.
virtual std::unique_ptr<RequireNode> resolvePathToNode(const std::string& path) const = 0;
// Get children of this node, if any (if this node represents a directory).
virtual std::vector<std::unique_ptr<RequireNode>> getChildren() const = 0;
// A list of the aliases available to this node.
virtual std::vector<RequireAlias> getAvailableAliases() const = 0;
};
struct RequireSuggestion
{
std::string label;
std::string fullPath;
std::vector<std::string> tags;
};
using RequireSuggestions = std::vector<RequireSuggestion>;
struct RequireSuggester
{
virtual ~RequireSuggester() {}
std::optional<RequireSuggestions> getRequireSuggestions(const ModuleName& requirer, const std::optional<std::string>& pathString) const;
protected:
virtual std::unique_ptr<RequireNode> getNode(const ModuleName& name) const = 0;
private:
std::optional<RequireSuggestions> getRequireSuggestionsImpl(const ModuleName& requirer, const std::optional<std::string>& path) const;
};
struct FileResolver
{
FileResolver() = default;
FileResolver(std::shared_ptr<RequireSuggester> requireSuggester)
: requireSuggester(std::move(requireSuggester))
{
}
virtual ~FileResolver() {}
virtual std::optional<SourceCode> readSource(const ModuleName& name) = 0;
@ -56,10 +117,9 @@ struct FileResolver
return std::nullopt;
}
virtual std::optional<RequireSuggestions> getRequireSuggestions(const ModuleName& requirer, const std::optional<std::string>& pathString) const
{
return std::nullopt;
}
std::optional<RequireSuggestions> getRequireSuggestions(const ModuleName& requirer, const std::optional<std::string>& pathString) const;
std::shared_ptr<RequireSuggester> requireSuggester;
};
struct NullFileResolver : FileResolver

View file

@ -3,9 +3,10 @@
#include "Luau/Ast.h"
#include "Luau/Parser.h"
#include "Luau/Autocomplete.h"
#include "Luau/AutocompleteTypes.h"
#include "Luau/DenseHash.h"
#include "Luau/Module.h"
#include "Luau/Frontend.h"
#include <memory>
#include <vector>
@ -14,12 +15,42 @@ namespace Luau
{
struct FrontendOptions;
enum class FragmentAutocompleteWaypoint
{
ParseFragmentEnd,
CloneModuleStart,
CloneModuleEnd,
DfgBuildEnd,
CloneAndSquashScopeStart,
CloneAndSquashScopeEnd,
ConstraintSolverStart,
ConstraintSolverEnd,
TypecheckFragmentEnd,
AutocompleteEnd,
COUNT,
};
class IFragmentAutocompleteReporter
{
public:
virtual void reportWaypoint(FragmentAutocompleteWaypoint) = 0;
virtual void reportFragmentString(std::string_view) = 0;
};
enum class FragmentTypeCheckStatus
{
SkipAutocomplete,
Success,
};
struct FragmentAutocompleteAncestryResult
{
DenseHashMap<AstName, AstLocal*> localMap{AstName()};
std::vector<AstLocal*> localStack;
std::vector<AstNode*> ancestry;
AstStat* nearestStatement = nullptr;
AstStatBlock* parentBlock = nullptr;
Location fragmentSelectionRegion;
};
struct FragmentParseResult
@ -27,35 +58,139 @@ struct FragmentParseResult
std::string fragmentToParse;
AstStatBlock* root = nullptr;
std::vector<AstNode*> ancestry;
AstStat* nearestStatement = nullptr;
std::vector<Comment> commentLocations;
std::unique_ptr<Allocator> alloc = std::make_unique<Allocator>();
Position scopePos{0, 0};
};
struct FragmentTypeCheckResult
{
ModulePtr incrementalModule = nullptr;
Scope* freshScope = nullptr;
ScopePtr freshScope;
std::vector<AstNode*> ancestry;
};
FragmentAutocompleteAncestryResult findAncestryForFragmentParse(AstStatBlock* root, const Position& cursorPos);
struct FragmentAutocompleteResult
{
ModulePtr incrementalModule;
Scope* freshScope;
TypeArena arenaForAutocomplete_DEPRECATED;
AutocompleteResult acResults;
};
FragmentParseResult parseFragment(const SourceModule& srcModule, std::string_view src, const Position& cursorPos);
struct FragmentRegion
{
Location fragmentLocation;
AstStat* nearestStatement = nullptr; // used for tests
AstStatBlock* parentBlock = nullptr; // used for scope detection
};
FragmentTypeCheckResult typecheckFragment(
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(
AstStatBlock* stale,
AstStatBlock* mostRecentParse,
AstNameTable* names,
std::string_view src,
const Position& cursorPos,
std::optional<Position> fragmentEndPosition
);
std::pair<FragmentTypeCheckStatus, FragmentTypeCheckResult> typecheckFragment(
Frontend& frontend,
const ModuleName& moduleName,
const Position& cursorPos,
std::optional<FrontendOptions> opts,
std::string_view src
std::string_view src,
std::optional<Position> fragmentEndPosition,
AstStatBlock* recentParse = nullptr,
IFragmentAutocompleteReporter* reporter = nullptr
);
AutocompleteResult fragmentAutocomplete(
FragmentAutocompleteResult fragmentAutocomplete(
Frontend& frontend,
std::string_view src,
const ModuleName& moduleName,
Position& cursorPosition,
Position cursorPosition,
std::optional<FrontendOptions> opts,
StringCompletionCallback callback
StringCompletionCallback callback,
std::optional<Position> fragmentEndPosition = std::nullopt,
AstStatBlock* recentParse = nullptr,
IFragmentAutocompleteReporter* reporter = nullptr
);
enum class FragmentAutocompleteStatus
{
Success,
FragmentTypeCheckFail,
InternalIce
};
struct FragmentAutocompleteStatusResult
{
FragmentAutocompleteStatus status;
std::optional<FragmentAutocompleteResult> result;
};
struct FragmentContext
{
std::string_view newSrc;
const ParseResult& freshParse;
std::optional<FrontendOptions> opts;
std::optional<Position> DEPRECATED_fragmentEndPosition;
IFragmentAutocompleteReporter* reporter = nullptr;
};
/**
* @brief Attempts to compute autocomplete suggestions from the fragment context.
*
* This function computes autocomplete suggestions using outdated frontend typechecking data
* by patching the fragment context of the new script source content.
*
* @param frontend The Luau Frontend data structure, which may contain outdated typechecking data.
*
* @param moduleName The name of the target module, specifying which script the caller wants to request autocomplete for.
*
* @param cursorPosition The position in the script where the caller wants to trigger autocomplete.
*
* @param context The fragment context that this API will use to patch the outdated typechecking data.
*
* @param stringCompletionCB A callback function that provides autocomplete suggestions for string contexts.
*
* @return
* The status indicating whether `fragmentAutocomplete` ran successfully or failed, along with the reason for failure.
* Also includes autocomplete suggestions if the status is successful.
*
* @usage
* FragmentAutocompleteStatusResult acStatusResult;
* if (shouldFragmentAC)
* acStatusResult = Luau::tryFragmentAutocomplete(...);
*
* if (acStatusResult.status != Successful)
* {
* frontend.check(moduleName, options);
* acStatusResult.acResult = Luau::autocomplete(...);
* }
* return convertResultWithContext(acStatusResult.acResult);
*/
FragmentAutocompleteStatusResult tryFragmentAutocomplete(
Frontend& frontend,
const ModuleName& moduleName,
Position cursorPosition,
FragmentContext context,
StringCompletionCallback stringCompletionCB
);
} // namespace Luau

View file

@ -7,9 +7,9 @@
#include "Luau/ModuleResolver.h"
#include "Luau/RequireTracer.h"
#include "Luau/Scope.h"
#include "Luau/Set.h"
#include "Luau/TypeCheckLimits.h"
#include "Luau/Variant.h"
#include "Luau/AnyTypeSummary.h"
#include <mutex>
#include <string>
@ -31,8 +31,8 @@ struct ModuleResolver;
struct ParseResult;
struct HotComment;
struct BuildQueueItem;
struct BuildQueueWorkState;
struct FrontendCancellationToken;
struct AnyTypeSummary;
struct LoadDefinitionFileResult
{
@ -56,13 +56,32 @@ struct SourceNode
return forAutocomplete ? dirtyModuleForAutocomplete : dirtyModule;
}
bool hasInvalidModuleDependency(bool forAutocomplete) const
{
return forAutocomplete ? invalidModuleDependencyForAutocomplete : invalidModuleDependency;
}
void setInvalidModuleDependency(bool value, bool forAutocomplete)
{
if (forAutocomplete)
invalidModuleDependencyForAutocomplete = value;
else
invalidModuleDependency = value;
}
ModuleName name;
std::string humanReadableName;
DenseHashSet<ModuleName> requireSet{{}};
std::vector<std::pair<ModuleName, Location>> requireLocations;
Set<ModuleName> dependents{{}};
bool dirtySourceModule = true;
bool dirtyModule = true;
bool dirtyModuleForAutocomplete = true;
bool invalidModuleDependency = true;
bool invalidModuleDependencyForAutocomplete = true;
double autocompleteLimitsMult = 1.0;
};
@ -117,7 +136,7 @@ struct FrontendModuleResolver : ModuleResolver
std::optional<ModuleInfo> resolveModuleInfo(const ModuleName& currentModuleName, const AstExpr& pathExpr) override;
std::string getHumanReadableModuleName(const ModuleName& moduleName) const override;
void setModule(const ModuleName& moduleName, ModulePtr module);
bool setModule(const ModuleName& moduleName, ModulePtr module);
void clearModules();
private:
@ -151,9 +170,13 @@ struct Frontend
// Parse and typecheck module graph
CheckResult check(const ModuleName& name, std::optional<FrontendOptions> optionOverride = {}); // new shininess
bool allModuleDependenciesValid(const ModuleName& name, bool forAutocomplete = false) const;
bool isDirty(const ModuleName& name, bool forAutocomplete = false) const;
void markDirty(const ModuleName& name, std::vector<ModuleName>* markedDirty = nullptr);
void traverseDependents(const ModuleName& name, std::function<bool(SourceNode&)> processSubtree);
/** Borrow a pointer into the SourceModule cache.
*
* Returns nullptr if we don't have it. This could mean that the script
@ -194,6 +217,7 @@ struct Frontend
);
std::optional<CheckResult> getCheckResult(const ModuleName& name, bool accumulateNested, bool forAutocomplete = false);
std::vector<ModuleName> getRequiredScripts(const ModuleName& name);
private:
ModulePtr check(
@ -226,6 +250,9 @@ private:
void checkBuildQueueItem(BuildQueueItem& item);
void checkBuildQueueItems(std::vector<BuildQueueItem>& items);
void recordItemResult(const BuildQueueItem& item);
void performQueueItemTask(std::shared_ptr<BuildQueueWorkState> state, size_t itemPos);
void sendQueueItemTask(std::shared_ptr<BuildQueueWorkState> state, size_t itemPos);
void sendQueueCycleItemTask(std::shared_ptr<BuildQueueWorkState> state);
static LintResult classifyLints(const std::vector<LintWarning>& warnings, const Config& config);
@ -271,6 +298,7 @@ ModulePtr check(
NotNull<ModuleResolver> moduleResolver,
NotNull<FileResolver> fileResolver,
const ScopePtr& globalScope,
const ScopePtr& typeFunctionScope,
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope,
FrontendOptions options,
TypeCheckLimits limits
@ -285,6 +313,7 @@ ModulePtr check(
NotNull<ModuleResolver> moduleResolver,
NotNull<FileResolver> fileResolver,
const ScopePtr& globalScope,
const ScopePtr& typeFunctionScope,
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope,
FrontendOptions options,
TypeCheckLimits limits,

View file

@ -8,12 +8,75 @@
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(
NotNull<TypeArena> arena,
NotNull<BuiltinTypes> builtinTypes,
NotNull<Scope> scope,
NotNull<DenseHashSet<TypeId>> bakedTypes,
NotNull<DenseHashSet<TypeId>> cachedTypes,
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;
SourceModule globalNames; // names for symbols entered into globalScope
ScopePtr globalScope; // shared by all modules
ScopePtr globalTypeFunctionScope; // shared by all modules
};
} // 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;
}
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
{
return pairs.begin();

View file

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

View file

@ -53,7 +53,7 @@ struct Replacer : Substitution
};
// A substitution which replaces generic functions by monomorphic functions
struct Instantiation2 : Substitution
struct Instantiation2 final : Substitution
{
// Mapping from generic types to free types to be used in instantiation.
DenseHashMap<TypeId, TypeId> genericSubstitutions{nullptr};

View file

@ -8,7 +8,6 @@
#include "Luau/ParseResult.h"
#include "Luau/Scope.h"
#include "Luau/TypeArena.h"
#include "Luau/AnyTypeSummary.h"
#include "Luau/DataFlowGraph.h"
#include <memory>
@ -19,8 +18,13 @@
namespace Luau
{
using LogLuauProc = void (*)(std::string_view, std::string_view);
extern LogLuauProc logLuau;
void setLogLuau(LogLuauProc ll);
void resetLogLuauProc();
struct Module;
struct AnyTypeSummary;
using ScopePtr = std::shared_ptr<struct Scope>;
using ModulePtr = std::shared_ptr<Module>;
@ -55,6 +59,7 @@ struct SourceModule
}
};
bool isWithinComment(const std::vector<Comment>& commentLocations, Position pos);
bool isWithinComment(const SourceModule& sourceModule, Position pos);
bool isWithinComment(const ParseResult& result, Position pos);
@ -68,19 +73,19 @@ struct Module
{
~Module();
// TODO: Clip this when we clip FFlagLuauSolverV2
bool checkedInNewSolver = false;
ModuleName name;
std::string humanReadableName;
TypeArena interfaceTypes;
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
std::shared_ptr<Allocator> allocator;
std::shared_ptr<AstNameTable> names;
AstStatBlock* root = nullptr;
std::vector<std::pair<Location, ScopePtr>> scopes; // never empty
@ -132,9 +137,11 @@ struct Module
TypePackId returnType = nullptr;
std::unordered_map<Name, TypeFun> exportedTypeBindings;
// We also need to keep DFG data alive between runs
std::shared_ptr<DataFlowGraph> dataFlowGraph = nullptr;
std::vector<std::unique_ptr<DfgScope>> dfgScopes;
// Arenas related to the DFG must persist after the DFG no longer exists, as
// Module objects maintain raw pointers to objects in these arenas.
DefArena defArena;
RefinementKeyArena keyArena;
bool hasModuleScope() const;
ScopePtr getModuleScope() const;

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
#pragma once
#include "Luau/DataFlowGraph.h"
#include "Luau/EqSatSimplification.h"
#include "Luau/Module.h"
#include "Luau/NotNull.h"
#include "Luau/DataFlowGraph.h"
namespace Luau
{
@ -15,6 +16,7 @@ struct TypeCheckLimits;
void checkNonStrict(
NotNull<BuiltinTypes> builtinTypes,
NotNull<Simplifier> simplifier,
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
NotNull<InternalErrorReporter> ice,
NotNull<UnifierSharedState> unifierState,

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

View file

@ -2,12 +2,13 @@
#pragma once
#include "Luau/Ast.h"
#include "Luau/InsertionOrderedMap.h"
#include "Luau/NotNull.h"
#include "Luau/TypeFwd.h"
#include "Luau/Location.h"
#include "Luau/EqSatSimplification.h"
#include "Luau/Error.h"
#include "Luau/InsertionOrderedMap.h"
#include "Luau/Location.h"
#include "Luau/NotNull.h"
#include "Luau/Subtyping.h"
#include "Luau/TypeFwd.h"
namespace Luau
{
@ -34,6 +35,7 @@ struct OverloadResolver
OverloadResolver(
NotNull<BuiltinTypes> builtinTypes,
NotNull<TypeArena> arena,
NotNull<Simplifier> simplifier,
NotNull<Normalizer> normalizer,
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
NotNull<Scope> scope,
@ -44,6 +46,7 @@ struct OverloadResolver
NotNull<BuiltinTypes> builtinTypes;
NotNull<TypeArena> arena;
NotNull<Simplifier> simplifier;
NotNull<Normalizer> normalizer;
NotNull<TypeFunctionRuntime> typeFunctionRuntime;
NotNull<Scope> scope;
@ -110,6 +113,7 @@ struct SolveResult
SolveResult solveFunctionCall(
NotNull<TypeArena> arena,
NotNull<BuiltinTypes> builtinTypes,
NotNull<Simplifier> simplifier,
NotNull<Normalizer> normalizer,
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
NotNull<InternalErrorReporter> iceReporter,

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);
// TODO: This is eerily similar to the pattern that NormalizedClassType
// TODO: This is eerily similar to the pattern that NormalizedExternType
// implements. We could, and perhaps should, merge them together.
template<typename K, typename V>
struct OrderedMap
@ -31,13 +31,4 @@ struct OrderedMap
}
};
struct QuantifierResult
{
TypeId result;
OrderedMap<TypeId, TypeId> insertedGenerics;
OrderedMap<TypePackId, TypePackId> insertedGenericPacks;
};
std::optional<QuantifierResult> quantify(TypeArena* arena, TypeId ty, Scope* scope);
} // namespace Luau

View file

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

View file

@ -11,14 +11,12 @@
namespace Luau
{
class AstStat;
class AstExpr;
class AstNode;
class AstStatBlock;
struct AstLocal;
struct RequireTraceResult
{
DenseHashMap<const AstExpr*, ModuleInfo> exprs{nullptr};
DenseHashMap<const AstNode*, ModuleInfo> exprs{nullptr};
std::vector<std::pair<ModuleName, Location>> requireList;
};

View file

@ -35,12 +35,12 @@ struct Scope
explicit Scope(TypePackId returnType); // root scope
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.
std::vector<NotNull<Scope>> children;
std::unordered_map<Symbol, Binding> bindings;
TypePackId returnType;
TypePackId returnType = nullptr;
std::optional<TypePackId> varargPack;
TypeLevel level;
@ -59,6 +59,8 @@ struct Scope
std::optional<TypeId> lookup(Symbol sym) const;
std::optional<TypeId> lookupUnrefinedType(DefId def) const;
std::optional<TypeId> lookupRValueRefinementType(DefId def) const;
std::optional<TypeId> lookup(DefId def) const;
std::optional<std::pair<TypeId, Scope*>> lookupEx(DefId def);
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)
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;
@ -85,12 +88,19 @@ struct Scope
void inheritAssignments(const ScopePtr& childScope);
void inheritRefinements(const ScopePtr& childScope);
// Track globals that should emit warnings during type checking.
DenseHashSet<std::string> globalsToWarn{""};
bool shouldWarnGlobal(std::string name) const;
// For mutually recursive type aliases, it's important that
// they use the same types for the same names.
// For instance, in `type Tree<T> { data: T, children: Forest<T> } type Forest<T> = {Tree<T>}`
// we need that the generic type `T` in both cases is the same, so we use a cache.
std::unordered_map<Name, TypeId> typeAliasTypeParameters;
std::unordered_map<Name, TypePackId> typeAliasTypePackParameters;
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

View file

@ -19,10 +19,13 @@ struct SimplifyResult
DenseHashSet<TypeId> blockedTypes;
};
SimplifyResult simplifyIntersection(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, TypeId ty, TypeId discriminant);
SimplifyResult simplifyIntersection(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, TypeId left, TypeId right);
SimplifyResult simplifyIntersection(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, std::set<TypeId> parts);
SimplifyResult simplifyUnion(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, TypeId ty, TypeId discriminant);
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
{

View file

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

View file

@ -1,13 +1,14 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "Luau/DenseHash.h"
#include "Luau/EqSatSimplification.h"
#include "Luau/Set.h"
#include "Luau/TypeCheckLimits.h"
#include "Luau/TypeFunction.h"
#include "Luau/TypeFwd.h"
#include "Luau/TypePairHash.h"
#include "Luau/TypePath.h"
#include "Luau/TypeFunction.h"
#include "Luau/TypeCheckLimits.h"
#include "Luau/DenseHash.h"
#include <vector>
#include <optional>
@ -21,7 +22,7 @@ struct InternalErrorReporter;
class TypeIds;
class Normalizer;
struct NormalizedClassType;
struct NormalizedExternType;
struct NormalizedFunctionType;
struct NormalizedStringType;
struct NormalizedType;
@ -120,7 +121,7 @@ struct SubtypingEnvironment
DenseHashMap<TypePackId, TypePackId> mappedGenericPacks{nullptr};
/*
* See the test cyclic_tables_are_assumed_to_be_compatible_with_classes for
* See the test cyclic_tables_are_assumed_to_be_compatible_with_extern_types for
* details.
*
* An empty value is equivalent to a nonexistent key.
@ -134,6 +135,7 @@ struct Subtyping
{
NotNull<BuiltinTypes> builtinTypes;
NotNull<TypeArena> arena;
NotNull<Simplifier> simplifier;
NotNull<Normalizer> normalizer;
NotNull<TypeFunctionRuntime> typeFunctionRuntime;
NotNull<InternalErrorReporter> iceReporter;
@ -155,6 +157,7 @@ struct Subtyping
Subtyping(
NotNull<BuiltinTypes> builtinTypes,
NotNull<TypeArena> typeArena,
NotNull<Simplifier> simplifier,
NotNull<Normalizer> normalizer,
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
NotNull<InternalErrorReporter> iceReporter
@ -226,9 +229,8 @@ private:
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TableType* subTable, const TableType* superTable, NotNull<Scope> scope);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const MetatableType* subMt, const MetatableType* superMt, NotNull<Scope> scope);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const MetatableType* subMt, const TableType* superTable, NotNull<Scope> scope);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const ClassType* subClass, const ClassType* superClass, NotNull<Scope> scope);
SubtypingResult
isCovariantWith(SubtypingEnvironment& env, TypeId subTy, const ClassType* subClass, TypeId superTy, const TableType* superTable, NotNull<Scope>);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const ExternType* subExternType, const ExternType* superExternType, NotNull<Scope> scope);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, TypeId subTy, const ExternType* subExternType, TypeId superTy, const TableType* superTable, NotNull<Scope>);
SubtypingResult isCovariantWith(
SubtypingEnvironment& env,
const FunctionType* subFunction,
@ -256,11 +258,11 @@ private:
);
SubtypingResult isCovariantWith(
SubtypingEnvironment& env,
const NormalizedClassType& subClass,
const NormalizedClassType& superClass,
const NormalizedExternType& subExternType,
const NormalizedExternType& superExternType,
NotNull<Scope> scope
);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const NormalizedClassType& subClass, const TypeIds& superTables, NotNull<Scope> scope);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const NormalizedExternType& subExternType, const TypeIds& superTables, NotNull<Scope> scope);
SubtypingResult isCovariantWith(
SubtypingEnvironment& env,
const NormalizedStringType& subString,

View file

@ -6,12 +6,15 @@
#include "Luau/NotNull.h"
#include "Luau/TypeFwd.h"
#include <vector>
namespace Luau
{
struct TypeArena;
struct BuiltinTypes;
struct Unifier2;
struct Subtyping;
class AstExpr;
TypeId matchLiteralType(
@ -20,6 +23,7 @@ TypeId matchLiteralType(
NotNull<BuiltinTypes> builtinTypes,
NotNull<TypeArena> arena,
NotNull<Unifier2> unifier,
NotNull<Subtyping> subtyping,
TypeId expectedType,
TypeId exprType,
const AstExpr* expr,

View file

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

View file

@ -65,11 +65,10 @@ T* getMutable(PendingTypePack* pending)
// Log of what TypeIds we are rebinding, to be committed later.
struct TxnLog
{
explicit TxnLog(bool useScopes = false)
explicit TxnLog()
: typeVarChanges(nullptr)
, typePackChanges(nullptr)
, ownedSeen()
, useScopes(useScopes)
, sharedSeen(&ownedSeen)
{
}
@ -193,16 +192,6 @@ struct TxnLog
// The pointer returned lives until `commit` or `clear` is called.
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
// indexer.
//

View file

@ -5,10 +5,11 @@
#include "Luau/Ast.h"
#include "Luau/Common.h"
#include "Luau/Refinement.h"
#include "Luau/DenseHash.h"
#include "Luau/NotNull.h"
#include "Luau/Polarity.h"
#include "Luau/Predicate.h"
#include "Luau/Refinement.h"
#include "Luau/Unifiable.h"
#include "Luau/Variant.h"
#include "Luau/VecDeque.h"
@ -19,7 +20,6 @@
#include <optional>
#include <set>
#include <string>
#include <unordered_map>
#include <vector>
LUAU_FASTINT(LuauTableTypeMaximumStringifierLength)
@ -31,6 +31,7 @@ namespace Luau
struct TypeArena;
struct Scope;
using ScopePtr = std::shared_ptr<Scope>;
struct Module;
struct TypeFunction;
struct Constraint;
@ -68,11 +69,11 @@ using Name = std::string;
// A free type is one whose exact shape has yet to be fully determined.
struct FreeType
{
explicit FreeType(TypeLevel level);
explicit FreeType(Scope* scope);
FreeType(Scope* scope, TypeLevel level);
FreeType(Scope* scope, TypeId lowerBound, TypeId upperBound);
// New constructors
explicit FreeType(TypeLevel level, TypeId lowerBound, TypeId upperBound);
// This one got promoted to explicit
explicit FreeType(Scope* scope, TypeId lowerBound, TypeId upperBound, Polarity polarity = Polarity::Unknown);
explicit FreeType(Scope* scope, TypeLevel level, TypeId lowerBound, TypeId upperBound);
int index;
TypeLevel level;
@ -86,6 +87,8 @@ struct FreeType
// Only used under local type inference
TypeId lowerBound = nullptr;
TypeId upperBound = nullptr;
Polarity polarity = Polarity::Unknown;
};
struct GenericType
@ -94,8 +97,8 @@ struct GenericType
GenericType();
explicit GenericType(TypeLevel level);
explicit GenericType(const Name& name);
explicit GenericType(Scope* scope);
explicit GenericType(const Name& name, Polarity polarity = Polarity::Unknown);
explicit GenericType(Scope* scope, Polarity polarity = Polarity::Unknown);
GenericType(TypeLevel level, const Name& name);
GenericType(Scope* scope, const Name& name);
@ -105,6 +108,8 @@ struct GenericType
Scope* scope = nullptr;
Name name;
bool explicitName = false;
Polarity polarity = Polarity::Unknown;
};
// When an equality constraint is found, it is then "bound" to that type,
@ -130,14 +135,14 @@ struct BlockedType
BlockedType();
int index;
Constraint* getOwner() const;
void setOwner(Constraint* newOwner);
void replaceOwner(Constraint* newOwner);
const Constraint* getOwner() const;
void setOwner(const Constraint* newOwner);
void replaceOwner(const Constraint* newOwner);
private:
// The constraint that is intended to unblock this type. Other constraints
// should block on this constraint if present.
Constraint* owner = nullptr;
const Constraint* owner = nullptr;
};
struct PrimitiveType
@ -278,19 +283,15 @@ struct WithPredicate
}
};
using MagicFunction = std::function<std::optional<
WithPredicate<TypePackId>>(struct TypeChecker&, const std::shared_ptr<struct Scope>&, const class AstExprCall&, WithPredicate<TypePackId>)>;
struct MagicFunctionCallContext
{
NotNull<struct ConstraintSolver> solver;
NotNull<const Constraint> constraint;
const class AstExprCall* callSite;
NotNull<const AstExprCall> callSite;
TypePackId arguments;
TypePackId result;
};
using DcrMagicFunction = std::function<bool(MagicFunctionCallContext)>;
struct MagicRefinementContext
{
NotNull<Scope> scope;
@ -307,8 +308,30 @@ struct MagicFunctionTypeCheckContext
NotNull<Scope> checkScope;
};
using DcrMagicRefinement = void (*)(const MagicRefinementContext&);
using DcrMagicFunctionTypeCheck = std::function<void(const MagicFunctionTypeCheckContext&)>;
struct MagicFunction
{
virtual std::optional<WithPredicate<TypePackId>>
handleOldSolver(struct TypeChecker&, const std::shared_ptr<struct Scope>&, const class AstExprCall&, WithPredicate<TypePackId>) = 0;
// Callback to allow custom typechecking of builtin function calls whose argument types
// will only be resolved after constraint solving. For example, the arguments to string.format
// have types that can only be decided after parsing the format string and unifying
// with the passed in values, but the correctness of the call can only be decided after
// all the types have been finalized.
virtual bool infer(const MagicFunctionCallContext&) = 0;
virtual void refine(const MagicRefinementContext&) {}
// If a magic function needs to do its own special typechecking, do it here.
// Returns true if magic typechecking was performed. Return false if the
// default typechecking logic should run.
virtual bool typeCheck(const MagicFunctionTypeCheckContext&)
{
return false;
}
virtual ~MagicFunction() {}
};
struct FunctionType
{
// Global monomorphic function
@ -325,10 +348,8 @@ struct FunctionType
);
// Local monomorphic function
FunctionType(TypeLevel level, TypePackId argTypes, TypePackId retTypes, std::optional<FunctionDefinition> defn = {}, bool hasSelf = false);
FunctionType(
TypeLevel level,
Scope* scope,
TypePackId argTypes,
TypePackId retTypes,
std::optional<FunctionDefinition> defn = {},
@ -345,16 +366,6 @@ struct FunctionType
std::optional<FunctionDefinition> defn = {},
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;
/// These should all be generic
@ -363,25 +374,16 @@ struct FunctionType
std::vector<std::optional<FunctionArgument>> argNames;
Tags tags;
TypeLevel level;
Scope* scope = nullptr;
TypePackId argTypes;
TypePackId retTypes;
MagicFunction magicFunction = nullptr;
DcrMagicFunction dcrMagicFunction = nullptr;
DcrMagicRefinement dcrMagicRefinement = nullptr;
// Callback to allow custom typechecking of builtin function calls whose argument types
// will only be resolved after constraint solving. For example, the arguments to string.format
// have types that can only be decided after parsing the format string and unifying
// with the passed in values, but the correctness of the call can only be decided after
// all the types have been finalized.
DcrMagicFunctionTypeCheck dcrMagicTypeCheck = nullptr;
std::shared_ptr<MagicFunction> magic = nullptr;
bool hasSelf;
// `hasNoFreeOrGenericTypes` should be true if and only if the type does not have any free or generic types present inside it.
// this flag is used as an optimization to exit early from procedures that manipulate free or generic types.
bool hasNoFreeOrGenericTypes = false;
bool isCheckedFunction = false;
bool isDeprecatedFunction = false;
};
enum class TableState
@ -458,7 +460,9 @@ struct Property
TypeId type() const;
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();
bool isShared() const;
@ -503,9 +507,6 @@ struct TableType
std::optional<TypeId> boundTo;
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.
// Some constraints will use this information to decide whether or not they
// are able to dispatch.
@ -531,15 +532,15 @@ struct ClassUserData
virtual ~ClassUserData() {}
};
/** The type of a class.
/** The type of an external userdata exposed to Luau.
*
* Classes behave like tables in many ways, but there are some important differences:
* Extern types behave like tables in many ways, but there are some important differences:
*
* The properties of a class are always exactly known.
* Classes optionally have a parent class.
* Two different classes that share the same properties are nevertheless distinct and mutually incompatible.
* Extern types optionally have a parent type.
* Two different extern types that share the same properties are nevertheless distinct and mutually incompatible.
*/
struct ClassType
struct ExternType
{
using Props = TableType::Props;
@ -553,7 +554,7 @@ struct ClassType
std::optional<Location> definitionLocation;
std::optional<TableIndexer> indexer;
ClassType(
ExternType(
Name name,
Props props,
std::optional<TypeId> parent,
@ -574,7 +575,7 @@ struct ClassType
{
}
ClassType(
ExternType(
Name name,
Props props,
std::optional<TypeId> parent,
@ -598,6 +599,18 @@ struct ClassType
}
};
// Data required to initialize a user-defined function and its environment
struct UserDefinedFunctionData
{
// Store a weak module reference to ensure the lifetime requirements are preserved
std::weak_ptr<Module> owner;
// References to AST elements are owned by the Module allocator which also stores this type
AstStatTypeFunction* definition = nullptr;
DenseHashMap<Name, std::pair<AstStatTypeFunction*, size_t>> environment{""};
};
/**
* An instance of a type function that has not yet been reduced to a more concrete
* type. The constraint solver receives a constraint to reduce each
@ -612,18 +625,21 @@ struct TypeFunctionInstanceType
std::vector<TypeId> typeArguments;
std::vector<TypePackId> packArguments;
std::optional<AstName> userFuncName; // Name of the user-defined type function; only available for UDTFs
std::optional<AstName> userFuncName; // Name of the user-defined type function; only available for UDTFs
UserDefinedFunctionData userFuncData;
TypeFunctionInstanceType(
NotNull<const TypeFunction> function,
std::vector<TypeId> typeArguments,
std::vector<TypePackId> packArguments,
std::optional<AstName> userFuncName = std::nullopt
std::optional<AstName> userFuncName,
UserDefinedFunctionData userFuncData
)
: function(function)
, typeArguments(typeArguments)
, packArguments(packArguments)
, userFuncName(userFuncName)
, userFuncData(userFuncData)
{
}
@ -640,6 +656,13 @@ struct TypeFunctionInstanceType
, packArguments(packArguments)
{
}
TypeFunctionInstanceType(NotNull<const TypeFunction> function, std::vector<TypeId> typeArguments, std::vector<TypePackId> packArguments)
: function{function}
, typeArguments(typeArguments)
, packArguments(packArguments)
{
}
};
/** Represents a pending type alias instantiation.
@ -739,7 +762,7 @@ struct NegationType
TypeId ty;
};
using ErrorType = Unifiable::Error;
using ErrorType = Unifiable::Error<TypeId>;
using TypeVariant = Unifiable::Variant<
TypeId,
@ -752,7 +775,7 @@ using TypeVariant = Unifiable::Variant<
FunctionType,
TableType,
MetatableType,
ClassType,
ExternType,
AnyType,
UnionType,
IntersectionType,
@ -845,6 +868,9 @@ struct TypeFun
*/
TypeId type;
// The location of where this TypeFun was defined, if available
std::optional<Location> definitionLocation;
TypeFun() = default;
explicit TypeFun(TypeId ty)
@ -852,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))
, 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))
, typePackParams(std::move(typePackParams))
, type(type)
, definitionLocation(definitionLocation)
{
}
@ -953,7 +986,7 @@ public:
const TypeId threadType;
const TypeId bufferType;
const TypeId functionType;
const TypeId classType;
const TypeId externType;
const TypeId tableType;
const TypeId emptyTableType;
const TypeId trueType;
@ -965,6 +998,7 @@ public:
const TypeId noRefineType;
const TypeId falsyType;
const TypeId truthyType;
const TypeId notNilType;
const TypeId optionalNumberType;
const TypeId optionalStringType;
@ -985,10 +1019,10 @@ TypeLevel* getMutableLevel(TypeId ty);
std::optional<TypeLevel> getLevel(TypePackId tp);
const Property* lookupClassProp(const ClassType* cls, const Name& name);
const Property* lookupExternTypeProp(const ExternType* cls, const Name& name);
// Whether `cls` is a subclass of `parent`
bool isSubclass(const ClassType* cls, const ClassType* parent);
bool isSubclass(const ExternType* cls, const ExternType* parent);
Type* asMutable(TypeId ty);
@ -1165,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)>;
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
#pragma once
#include "Luau/Polarity.h"
#include "Luau/TypedAllocator.h"
#include "Luau/Type.h"
#include "Luau/TypePack.h"
@ -32,11 +33,11 @@ struct TypeArena
TypeId addTV(Type&& tv);
TypeId freshType(TypeLevel level);
TypeId freshType(Scope* scope);
TypeId freshType(Scope* scope, TypeLevel level);
TypeId freshType(NotNull<BuiltinTypes> builtins, TypeLevel level);
TypeId freshType(NotNull<BuiltinTypes> builtins, Scope* scope);
TypeId freshType(NotNull<BuiltinTypes> builtins, Scope* scope, TypeLevel level);
TypePackId freshTypePack(Scope* scope);
TypePackId freshTypePack(Scope* scope, Polarity polarity = Polarity::Unknown);
TypePackId addTypePack(std::initializer_list<TypeId> types);
TypePackId addTypePack(std::vector<TypeId> types, std::optional<TypePackId> tail = {});

View file

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

View file

@ -2,15 +2,18 @@
#pragma once
#include "Luau/Error.h"
#include "Luau/NotNull.h"
#include "Luau/Common.h"
#include "Luau/TypeUtils.h"
#include "Luau/EqSatSimplification.h"
#include "Luau/Error.h"
#include "Luau/Normalize.h"
#include "Luau/NotNull.h"
#include "Luau/Subtyping.h"
#include "Luau/Type.h"
#include "Luau/TypeFwd.h"
#include "Luau/TypeOrPack.h"
#include "Luau/Normalize.h"
#include "Luau/Subtyping.h"
#include "Luau/TypeUtils.h"
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
namespace Luau
{
@ -37,18 +40,29 @@ struct Reasonings
std::string toString()
{
if (FFlag::LuauImproveTypePathsInErrors && reasons.empty())
return "";
// DenseHashSet ordering is entirely undefined, so we want to
// sort the reasons here to achieve a stable error
// stringification.
std::sort(reasons.begin(), reasons.end());
std::string allReasons;
std::string allReasons = FFlag::LuauImproveTypePathsInErrors ? "\nthis is because " : "";
bool first = true;
for (const std::string& reason : reasons)
{
if (first)
first = false;
if (FFlag::LuauImproveTypePathsInErrors)
{
if (reasons.size() > 1)
allReasons += "\n\t * ";
}
else
allReasons += "\n\t";
{
if (first)
first = false;
else
allReasons += "\n\t";
}
allReasons += reason;
}
@ -60,8 +74,9 @@ struct Reasonings
void check(
NotNull<BuiltinTypes> builtinTypes,
NotNull<Simplifier> simplifier,
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
NotNull<UnifierSharedState> sharedState,
NotNull<UnifierSharedState> unifierState,
NotNull<TypeCheckLimits> limits,
DcrLogger* logger,
const SourceModule& sourceModule,
@ -71,6 +86,7 @@ void check(
struct TypeChecker2
{
NotNull<BuiltinTypes> builtinTypes;
NotNull<Simplifier> simplifier;
NotNull<TypeFunctionRuntime> typeFunctionRuntime;
DcrLogger* logger;
const NotNull<TypeCheckLimits> limits;
@ -90,6 +106,7 @@ struct TypeChecker2
TypeChecker2(
NotNull<BuiltinTypes> builtinTypes,
NotNull<Simplifier> simplifier,
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
NotNull<UnifierSharedState> unifierState,
NotNull<TypeCheckLimits> limits,
@ -112,14 +129,14 @@ private:
std::optional<StackPusher> pushStack(AstNode* node);
void checkForInternalTypeFunction(TypeId ty, Location location);
TypeId checkForTypeFunctionInhabitance(TypeId instance, Location location);
TypePackId lookupPack(AstExpr* expr);
TypePackId lookupPack(AstExpr* expr) const;
TypeId lookupType(AstExpr* expr);
TypeId lookupAnnotation(AstType* annotation);
std::optional<TypePackId> lookupPackAnnotation(AstTypePack* annotation);
TypeId lookupExpectedType(AstExpr* expr);
TypePackId lookupExpectedPack(AstExpr* expr, TypeArena& arena);
std::optional<TypePackId> lookupPackAnnotation(AstTypePack* annotation) const;
TypeId lookupExpectedType(AstExpr* expr) const;
TypePackId lookupExpectedPack(AstExpr* expr, TypeArena& arena) const;
TypePackId reconstructPack(AstArray<AstExpr*> exprs, TypeArena& arena);
Scope* findInnermostScope(Location location);
Scope* findInnermostScope(Location location) const;
void visit(AstStat* stat);
void visit(AstStatIf* ifStatement);
void visit(AstStatWhile* whileStatement);
@ -143,7 +160,7 @@ private:
void visit(AstTypeList types);
void visit(AstStatDeclareFunction* stat);
void visit(AstStatDeclareGlobal* stat);
void visit(AstStatDeclareClass* stat);
void visit(AstStatDeclareExternType* stat);
void visit(AstStatError* stat);
void visit(AstExpr* expr, ValueContext context);
void visit(AstExprGroup* expr, ValueContext context);
@ -156,7 +173,7 @@ private:
void visit(AstExprVarargs* expr);
void visitCall(AstExprCall* call);
void visit(AstExprCall* call);
std::optional<TypeId> tryStripUnionFromNil(TypeId ty);
std::optional<TypeId> tryStripUnionFromNil(TypeId ty) const;
TypeId stripFromNilAndReport(TypeId ty, const Location& location);
void visitExprName(AstExpr* expr, Location location, const std::string& propName, ValueContext context, TypeId astIndexExprTy);
void visit(AstExprIndexName* indexName, ValueContext context);
@ -171,7 +188,7 @@ private:
void visit(AstExprInterpString* interpString);
void visit(AstExprError* expr);
TypeId flattenPack(TypePackId pack);
void visitGenerics(AstArray<AstGenericType> generics, AstArray<AstGenericTypePack> genericPacks);
void visitGenerics(AstArray<AstGenericType*> generics, AstArray<AstGenericTypePack*> genericPacks);
void visit(AstType* ty);
void visit(AstTypeReference* ty);
void visit(AstTypeTable* table);
@ -213,6 +230,9 @@ private:
std::vector<TypeError>& errors
);
// Avoid duplicate warnings being emitted for the same global variable.
DenseHashSet<std::string> warnedGlobals{""};
void diagnoseMissingTableKey(UnknownProperty* utk, TypeErrorData& data) const;
bool isErrorSuppressing(Location loc, TypeId ty);
bool isErrorSuppressing(Location loc1, TypeId ty1, Location loc2, TypeId ty2);

View file

@ -2,6 +2,7 @@
#pragma once
#include "Luau/Constraint.h"
#include "Luau/EqSatSimplification.h"
#include "Luau/Error.h"
#include "Luau/NotNull.h"
#include "Luau/TypeCheckLimits.h"
@ -41,9 +42,18 @@ struct TypeFunctionRuntime
StateRef state;
// Set of functions which have their environment table initialized
DenseHashSet<AstStatTypeFunction*> initialized{nullptr};
// Evaluation of type functions should only be performed in the absence of parse errors in the source module
bool allowEvaluation = true;
// Root scope in which the type function operates in, set up by ConstraintGenerator
ScopePtr rootScope;
// Output created by 'print' function
std::vector<std::string> messages;
private:
void prepareState();
};
@ -53,6 +63,7 @@ struct TypeFunctionContext
NotNull<TypeArena> arena;
NotNull<BuiltinTypes> builtins;
NotNull<Scope> scope;
NotNull<Simplifier> simplifier;
NotNull<Normalizer> normalizer;
NotNull<TypeFunctionRuntime> typeFunctionRuntime;
NotNull<InternalErrorReporter> ice;
@ -63,7 +74,7 @@ struct TypeFunctionContext
// The constraint being reduced in this run of the reduction
const Constraint* constraint;
std::optional<AstName> userFuncName; // Name of the user-defined type function; only available for UDTFs
std::optional<AstName> userFuncName; // Name of the user-defined type function; only available for UDTFs
TypeFunctionContext(NotNull<ConstraintSolver> cs, NotNull<Scope> scope, NotNull<const Constraint> constraint);
@ -71,6 +82,7 @@ struct TypeFunctionContext
NotNull<TypeArena> arena,
NotNull<BuiltinTypes> builtins,
NotNull<Scope> scope,
NotNull<Simplifier> simplifier,
NotNull<Normalizer> normalizer,
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
NotNull<InternalErrorReporter> ice,
@ -79,6 +91,7 @@ struct TypeFunctionContext
: arena(arena)
, builtins(builtins)
, scope(scope)
, simplifier(simplifier)
, normalizer(normalizer)
, typeFunctionRuntime(typeFunctionRuntime)
, ice(ice)
@ -91,19 +104,31 @@ struct TypeFunctionContext
NotNull<Constraint> pushConstraint(ConstraintV&& c) const;
};
enum class Reduction
{
// The type function is either known to be reducible or the determination is blocked.
MaybeOk,
// The type function is known to be irreducible, but maybe not be erroneous, e.g. when it's over generics or free types.
Irreducible,
// The type function is known to be irreducible, and is definitely erroneous.
Erroneous,
};
/// Represents a reduction result, which may have successfully reduced the type,
/// may have concretely failed to reduce the type, or may simply be stuck
/// without more information.
template<typename Ty>
struct TypeFunctionReductionResult
{
/// The result of the reduction, if any. If this is nullopt, the type function
/// could not be reduced.
std::optional<Ty> result;
/// Whether the result is uninhabited: whether we know, unambiguously and
/// permanently, whether this type function reduction results in an
/// uninhabitable type. This will trigger an error to be reported.
bool uninhabited;
/// Indicates the status of this reduction: is `Reduction::Irreducible` if
/// the this result indicates the type function is irreducible, and
/// `Reduction::Erroneous` if this result indicates the type function is
/// erroneous. `Reduction::MaybeOk` otherwise.
Reduction reductionStatus;
/// Any types that need to be progressed or mutated before the reduction may
/// proceed.
std::vector<TypeId> blockedTypes;
@ -112,6 +137,8 @@ struct TypeFunctionReductionResult
std::vector<TypePackId> blockedPacks;
/// A runtime error message from user-defined type functions
std::optional<std::string> error;
/// Messages printed out from user-defined type functions
std::vector<std::string> messages;
};
template<typename T>
@ -128,6 +155,9 @@ struct TypeFunction
/// The reducer function for the type function.
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
@ -140,15 +170,20 @@ struct TypePackFunction
/// The reducer function for the type pack function.
ReducerFunction<TypePackId> reducer;
/// If true, this type function can reduce even if it is parameterized on a generic.
bool canReduceGenerics = false;
};
struct FunctionGraphReductionResult
{
ErrorVec errors;
ErrorVec messages;
DenseHashSet<TypeId> blockedTypes{nullptr};
DenseHashSet<TypePackId> blockedPacks{nullptr};
DenseHashSet<TypeId> reducedTypes{nullptr};
DenseHashSet<TypePackId> reducedPacks{nullptr};
DenseHashSet<TypeId> irreducibleTypes{nullptr};
};
/**
@ -216,6 +251,11 @@ struct BuiltinTypeFunctions
TypeFunction indexFunc;
TypeFunction rawgetFunc;
TypeFunction setmetatableFunc;
TypeFunction getmetatableFunc;
TypeFunction weakoptionalFunc;
void addToScope(NotNull<TypeArena> arena, NotNull<Scope> scope) const;
};

View file

@ -3,6 +3,7 @@
#include "Luau/Common.h"
#include "Luau/Variant.h"
#include "Luau/TypeFwd.h"
#include <optional>
#include <string>
@ -31,6 +32,8 @@ struct TypeFunctionPrimitiveType
Boolean,
Number,
String,
Thread,
Buffer,
};
Type type;
@ -117,7 +120,14 @@ struct TypeFunctionVariadicTypePack
TypeFunctionTypeId type;
};
using TypeFunctionTypePackVariant = Variant<TypeFunctionTypePack, TypeFunctionVariadicTypePack>;
struct TypeFunctionGenericTypePack
{
bool isNamed = false;
std::string name;
};
using TypeFunctionTypePackVariant = Variant<TypeFunctionTypePack, TypeFunctionVariadicTypePack, TypeFunctionGenericTypePack>;
struct TypeFunctionTypePackVar
{
@ -133,6 +143,9 @@ struct TypeFunctionTypePackVar
struct TypeFunctionFunctionType
{
std::vector<TypeFunctionTypeId> generics;
std::vector<TypeFunctionTypePackId> genericPacks;
TypeFunctionTypePackId argTypes;
TypeFunctionTypePackId retTypes;
};
@ -192,7 +205,7 @@ struct TypeFunctionTableType
std::optional<TypeFunctionTypeId> metatable;
};
struct TypeFunctionClassType
struct TypeFunctionExternType
{
using Name = std::string;
using Props = std::map<Name, TypeFunctionProperty>;
@ -203,7 +216,19 @@ struct TypeFunctionClassType
std::optional<TypeFunctionTypeId> metatable; // metaclass?
std::optional<TypeFunctionTypeId> parent;
// this was mistaken, and we should actually be keeping separate read/write types here.
std::optional<TypeFunctionTypeId> parent_DEPRECATED;
std::optional<TypeFunctionTypeId> readParent;
std::optional<TypeFunctionTypeId> writeParent;
TypeId externTy;
};
struct TypeFunctionGenericType
{
bool isNamed = false;
bool isPack = false;
std::string name;
};
@ -219,7 +244,8 @@ using TypeFunctionTypeVariant = Luau::Variant<
TypeFunctionNegationType,
TypeFunctionFunctionType,
TypeFunctionTableType,
TypeFunctionClassType>;
TypeFunctionExternType,
TypeFunctionGenericType>;
struct TypeFunctionType
{

View file

@ -28,20 +28,12 @@ struct TypeFunctionRuntimeBuilderState
{
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{{}};
// 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{};
TypeFunctionRuntimeBuilderState(NotNull<TypeFunctionContext> ctx)
: ctx(ctx)
, classesSerialized({})
, errors({})
{
}
};

View file

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

View file

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

View file

@ -1,11 +1,12 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "Luau/Common.h"
#include "Luau/NotNull.h"
#include "Luau/Polarity.h"
#include "Luau/TypeFwd.h"
#include "Luau/Unifiable.h"
#include "Luau/Variant.h"
#include "Luau/TypeFwd.h"
#include "Luau/NotNull.h"
#include "Luau/Common.h"
#include <optional>
#include <set>
@ -26,12 +27,14 @@ struct TypeFunctionInstanceTypePack;
struct FreeTypePack
{
explicit FreeTypePack(TypeLevel level);
explicit FreeTypePack(Scope* scope);
explicit FreeTypePack(Scope* scope, Polarity polarity = Polarity::Unknown);
FreeTypePack(Scope* scope, TypeLevel level);
int index;
TypeLevel level;
Scope* scope = nullptr;
Polarity polarity = Polarity::Unknown;
};
struct GenericTypePack
@ -40,7 +43,7 @@ struct GenericTypePack
GenericTypePack();
explicit GenericTypePack(TypeLevel level);
explicit GenericTypePack(const Name& name);
explicit GenericTypePack(Scope* scope);
explicit GenericTypePack(Scope* scope, Polarity polarity = Polarity::Unknown);
GenericTypePack(TypeLevel level, const Name& name);
GenericTypePack(Scope* scope, const Name& name);
@ -49,10 +52,12 @@ struct GenericTypePack
Scope* scope = nullptr;
Name name;
bool explicitName = false;
Polarity polarity = Polarity::Unknown;
};
using BoundTypePack = Unifiable::Bound<TypePackId>;
using ErrorTypePack = Unifiable::Error;
using ErrorTypePack = Unifiable::Error<TypePackId>;
using TypePackVariant =
Unifiable::Variant<TypePackId, FreeTypePack, GenericTypePack, TypePack, VariadicTypePack, BlockedTypePack, TypeFunctionInstanceTypePack>;
@ -100,9 +105,9 @@ struct TypeFunctionInstanceTypePack
struct TypePackVar
{
explicit TypePackVar(const TypePackVariant& ty);
explicit TypePackVar(TypePackVariant&& ty);
TypePackVar(TypePackVariant&& ty, bool persistent);
explicit TypePackVar(const TypePackVariant& tp);
explicit TypePackVar(TypePackVariant&& tp);
TypePackVar(TypePackVariant&& tp, bool persistent);
bool operator==(const TypePackVar& rhs) const;
@ -169,6 +174,7 @@ struct TypePackIterator
private:
TypePackId currentTypePack = nullptr;
TypePackId tailCycleCheck = nullptr;
const TypePack* tp = nullptr;
size_t currentIndex = 0;
@ -179,6 +185,8 @@ TypePackIterator begin(TypePackId tp);
TypePackIterator begin(TypePackId tp, const TxnLog* log);
TypePackIterator end(TypePackId tp);
TypePackId getTail(TypePackId tp);
using SeenSet = std::set<std::pair<const void*, const void*>>;
bool areEqual(SeenSet& seen, const TypePackVar& lhs, const TypePackVar& rhs);

View file

@ -42,9 +42,19 @@ struct Property
/// element.
struct Index
{
enum class Variant
{
Pack,
Union,
Intersection
};
/// The 0-based index to use for the lookup.
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;
};
@ -205,6 +215,9 @@ using Path = TypePath::Path;
/// terribly clear to end users of the Luau type system.
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(TypePackId root, const Path& path, NotNull<BuiltinTypes> builtinTypes);

View file

@ -40,7 +40,7 @@ struct InConditionalContext
TypeContext* typeContext;
TypeContext oldValue;
InConditionalContext(TypeContext* c)
explicit InConditionalContext(TypeContext* c)
: typeContext(c)
, oldValue(*c)
{
@ -269,8 +269,8 @@ bool isLiteral(const AstExpr* expr);
std::vector<TypeId> findBlockedTypesIn(AstExprTable* expr, NotNull<DenseHashMap<const AstExpr*, TypeId>> astTypes);
/**
* Given a function call and a mapping from expression to type, determine
* whether the type of any argument in said call in depends on a blocked types.
* Given a function call and a mapping from expression to type, determine
* whether the type of any argument in said call in depends on a blocked types.
* This is used as a precondition for bidirectional inference: be warned that
* the behavior of this algorithm is tightly coupled to that of bidirectional
* inference.
@ -280,4 +280,15 @@ std::vector<TypeId> findBlockedTypesIn(AstExprTable* expr, NotNull<DenseHashMap<
*/
std::vector<TypeId> findBlockedArgTypesIn(AstExprCall* expr, NotNull<DenseHashMap<const AstExpr*, TypeId>> astTypes);
/**
* Given a scope and a free type, find the closest parent that has a present
* `interiorFreeTypes` and append the given type to said list. This list will
* be generalized when the requiste `GeneralizationConstraint` is resolved.
* @param scope Initial scope this free type was attached to
* @param ty Free type to track.
*/
void trackInteriorFreeType(Scope* scope, TypeId ty);
void trackInteriorFreeTypePack(Scope* scope, TypePackId tp);
} // namespace Luau

View file

@ -3,6 +3,7 @@
#include "Luau/Variant.h"
#include <optional>
#include <string>
namespace Luau
@ -94,19 +95,29 @@ struct Bound
Id boundTo;
};
template<typename Id>
struct Error
{
// This constructor has to be public, since it's used in Type and TypePack,
// but shouldn't be called directly. Please use errorRecoveryType() instead.
Error();
explicit Error();
explicit Error(Id synthetic)
: synthetic{synthetic}
{
}
int index;
// This is used to create an error that can be rendered out using this field
// as appropriate metadata for communicating it to the user.
std::optional<Id> synthetic;
private:
static int nextIndex;
};
template<typename Id, typename... Value>
using Variant = Luau::Variant<Bound<Id>, Error, Value...>;
using Variant = Luau::Variant<Bound<Id>, Error<Id>, Value...>;
} // namespace Luau::Unifiable

View file

@ -93,10 +93,6 @@ struct Unifier
Unifier(NotNull<Normalizer> normalizer, NotNull<Scope> scope, const Location& location, Variance variance, TxnLog* parentLog = nullptr);
// Configure the Unifier to test for scope subsumption via embedded Scope
// pointers rather than TypeLevels.
void enableNewSolver();
// Test whether the two type vars unify. Never commits the result.
ErrorVec canUnify(TypeId subTy, TypeId superTy);
ErrorVec canUnify(TypePackId subTy, TypePackId superTy, bool isFunctionCall = false);
@ -144,7 +140,7 @@ private:
void tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection = false, const LiteralProperties* aliasableMap = nullptr);
void tryUnifyScalarShape(TypeId subTy, TypeId superTy, bool reversed);
void tryUnifyWithMetatable(TypeId subTy, TypeId superTy, bool reversed);
void tryUnifyWithClass(TypeId subTy, TypeId superTy, bool reversed);
void tryUnifyWithExternType(TypeId subTy, TypeId superTy, bool reversed);
void tryUnifyNegations(TypeId subTy, TypeId superTy);
TypePackId tryApplyOverloadedFunction(TypeId function, const NormalizedFunctionType& overloads, TypePackId args);
@ -169,7 +165,6 @@ private:
std::optional<TypeId> findTablePropertyRespectingMeta(TypeId lhsType, Name name);
TxnLog combineLogsIntoIntersection(std::vector<TxnLog> logs);
TxnLog combineLogsIntoUnion(std::vector<TxnLog> logs);
public:
@ -195,11 +190,6 @@ private:
// Available after regular type pack unification errors
std::optional<int> firstPackErrorPos;
// If true, we do a bunch of small things differently to work better with
// the new type inference engine. Most notably, we use the Scope hierarchy
// directly rather than using TypeLevels.
bool useNewSolver = false;
};
void promoteTypeLevels(TxnLog& log, const TypeArena* arena, TypeLevel minLevel, Scope* outerScope, bool useScope, TypePackId tp);

View file

@ -44,6 +44,12 @@ struct Unifier2
// Mapping from generic type packs to `TypePack`s of free types to be used in instantiation.
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 recursionLimit = 0;
@ -87,6 +93,9 @@ struct Unifier2
bool unify(const AnyType* subAny, const TableType* superTable);
bool unify(const TableType* subTable, const AnyType* superAny);
bool unify(const MetatableType* subMetatable, const AnyType*);
bool unify(const AnyType*, const MetatableType* superMetatable);
// TODO think about this one carefully. We don't do unions or intersections of type packs
bool unify(TypePackId subTp, TypePackId superTp);
@ -110,6 +119,9 @@ private:
// Returns true if needle occurs within haystack already. ie if we bound
// needle to haystack, would a cyclic TypePack result?
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

View file

@ -49,6 +49,26 @@ struct UnifierSharedState
DenseHashSet<TypePackId> tempSeenTp{nullptr};
UnifierCounters counters;
bool reentrantTypeReduction = false;
};
struct TypeReductionRentrancyGuard final
{
explicit TypeReductionRentrancyGuard(NotNull<UnifierSharedState> sharedState)
: sharedState{sharedState}
{
sharedState->reentrantTypeReduction = true;
}
~TypeReductionRentrancyGuard()
{
sharedState->reentrantTypeReduction = false;
}
TypeReductionRentrancyGuard(const TypeReductionRentrancyGuard&) = delete;
TypeReductionRentrancyGuard(TypeReductionRentrancyGuard&&) = delete;
private:
NotNull<UnifierSharedState> sharedState;
};
} // namespace Luau

View file

@ -10,7 +10,6 @@
#include "Type.h"
LUAU_FASTINT(LuauVisitRecursionLimit)
LUAU_FASTFLAG(LuauBoundLazyTypes2)
LUAU_FASTFLAG(LuauSolverV2)
namespace Luau
@ -86,6 +85,8 @@ struct GenericTypeVisitor
{
}
virtual ~GenericTypeVisitor() {}
virtual void cycle(TypeId) {}
virtual void cycle(TypePackId) {}
@ -125,7 +126,7 @@ struct GenericTypeVisitor
{
return visit(ty);
}
virtual bool visit(TypeId ty, const ClassType& ctv)
virtual bool visit(TypeId ty, const ExternType& etv)
{
return visit(ty);
}
@ -190,7 +191,7 @@ struct GenericTypeVisitor
{
return visit(tp);
}
virtual bool visit(TypePackId tp, const Unifiable::Error& etp)
virtual bool visit(TypePackId tp, const ErrorTypePack& etp)
{
return visit(tp);
}
@ -312,11 +313,11 @@ struct GenericTypeVisitor
traverse(mtv->metatable);
}
}
else if (auto ctv = get<ClassType>(ty))
else if (auto etv = get<ExternType>(ty))
{
if (visit(ty, *ctv))
if (visit(ty, *etv))
{
for (const auto& [name, prop] : ctv->props)
for (const auto& [name, prop] : etv->props)
{
if (FFlag::LuauSolverV2)
{
@ -334,16 +335,16 @@ struct GenericTypeVisitor
traverse(prop.type());
}
if (ctv->parent)
traverse(*ctv->parent);
if (etv->parent)
traverse(*etv->parent);
if (ctv->metatable)
traverse(*ctv->metatable);
if (etv->metatable)
traverse(*etv->metatable);
if (ctv->indexer)
if (etv->indexer)
{
traverse(ctv->indexer->indexType);
traverse(ctv->indexer->indexResultType);
traverse(etv->indexer->indexType);
traverse(etv->indexer->indexResultType);
}
}
}
@ -395,7 +396,7 @@ struct GenericTypeVisitor
traverse(unwrapped);
// Visiting into LazyType that hasn't been unwrapped may necessarily cause infinite expansion, so we don't do that on purpose.
// Asserting also makes no sense, because the type _will_ happen here, most likely as a property of some ClassType
// Asserting also makes no sense, because the type _will_ happen here, most likely as a property of some ExternType
// that doesn't need to be expanded.
}
else if (auto stv = get<SingletonType>(ty))
@ -461,7 +462,7 @@ struct GenericTypeVisitor
else if (auto gtv = get<GenericTypePack>(tp))
visit(tp, *gtv);
else if (auto etv = get<Unifiable::Error>(tp))
else if (auto etv = get<ErrorTypePack>(tp))
visit(tp, *etv);
else if (auto pack = get<TypePack>(tp))

View file

@ -1,903 +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)
{
if (get<ClassType>(ty))
if (get<ExternType>(ty))
return true;
return ty->persistent;

View file

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

View file

@ -8,6 +8,8 @@
#include <math.h>
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
namespace Luau
{
@ -431,8 +433,16 @@ struct AstJsonEncoder : public AstVisitor
if (node->self)
PROP(self);
PROP(args);
if (node->returnAnnotation)
PROP(returnAnnotation);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
if (node->returnAnnotation)
PROP(returnAnnotation);
}
else
{
if (node->returnAnnotation_DEPRECATED)
write("returnAnnotation", node->returnAnnotation_DEPRECATED);
}
PROP(vararg);
PROP(varargLocation);
if (node->varargAnnotation)
@ -465,26 +475,26 @@ struct AstJsonEncoder : public AstVisitor
writeRaw("}");
}
void write(const AstGenericType& genericType)
void write(class AstGenericType* genericType)
{
writeRaw("{");
bool c = pushComma();
writeType("AstGenericType");
write("name", genericType.name);
if (genericType.defaultValue)
write("luauType", genericType.defaultValue);
write("name", genericType->name);
if (genericType->defaultValue)
write("luauType", genericType->defaultValue);
popComma(c);
writeRaw("}");
}
void write(const AstGenericTypePack& genericTypePack)
void write(class AstGenericTypePack* genericTypePack)
{
writeRaw("{");
bool c = pushComma();
writeType("AstGenericTypePack");
write("name", genericTypePack.name);
if (genericTypePack.defaultValue)
write("luauType", genericTypePack.defaultValue);
write("name", genericTypePack->name);
if (genericTypePack->defaultValue)
write("luauType", genericTypePack->defaultValue);
popComma(c);
writeRaw("}");
}
@ -902,7 +912,10 @@ struct AstJsonEncoder : public AstVisitor
PROP(paramNames);
PROP(vararg);
PROP(varargLocation);
PROP(retTypes);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
PROP(retTypes);
else
write("retTypes", node->retTypes_DEPRECATED);
PROP(generics);
PROP(genericPacks);
}
@ -923,7 +936,7 @@ struct AstJsonEncoder : public AstVisitor
);
}
void write(const AstDeclaredClassProp& prop)
void write(const AstDeclaredExternTypeProperty& prop)
{
writeRaw("{");
bool c = pushComma();
@ -936,7 +949,7 @@ struct AstJsonEncoder : public AstVisitor
writeRaw("}");
}
void write(class AstStatDeclareClass* node)
void write(class AstStatDeclareExternType* node)
{
writeNode(
node,
@ -1048,7 +1061,10 @@ struct AstJsonEncoder : public AstVisitor
PROP(genericPacks);
PROP(argTypes);
PROP(argNames);
PROP(returnTypes);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
PROP(returnTypes);
else
write("returnTypes", node->returnTypes_DEPRECATED);
}
);
}
@ -1065,6 +1081,11 @@ struct AstJsonEncoder : public AstVisitor
);
}
void write(class AstTypeOptional* node)
{
writeNode(node, "AstTypeOptional", [&]() {});
}
void write(class AstTypeUnion* node)
{
writeNode(
@ -1146,6 +1167,8 @@ struct AstJsonEncoder : public AstVisitor
return writeString("checked");
case AstAttr::Type::Native:
return writeString("native");
case AstAttr::Type::Deprecated:
return writeString("deprecated");
}
}
@ -1161,6 +1184,19 @@ struct AstJsonEncoder : public AstVisitor
);
}
bool visit(class AstTypeGroup* node) override
{
writeNode(
node,
"AstTypeGroup",
[&]()
{
write("inner", node->type);
}
);
return false;
}
bool visit(class AstTypeSingletonBool* node) override
{
writeNode(
@ -1409,7 +1445,7 @@ struct AstJsonEncoder : public AstVisitor
return false;
}
bool visit(class AstStatDeclareClass* node) override
bool visit(class AstStatDeclareExternType* node) override
{
write(node);
return false;

View file

@ -13,8 +13,6 @@
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAGVARIABLE(LuauDocumentationAtPosition)
namespace Luau
{
@ -43,11 +41,15 @@ struct AutocompleteNodeFinder : public AstVisitor
bool visit(AstStat* stat) override
{
if (stat->location.begin < pos && pos <= stat->location.end)
// Consider 'local myLocal = 4;|' and 'local myLocal = 4', where '|' is the cursor position. In both cases, the cursor position is equal
// to `AstStatLocal.location.end`. However, in the first case (semicolon), we are starting a new statement, whilst in the second case
// (no semicolon) we are still part of the AstStatLocal, hence the different comparison check.
if (stat->location.begin < pos && (stat->hasSemicolon ? pos < stat->location.end : pos <= stat->location.end))
{
ancestry.push_back(stat);
return true;
}
return false;
}
@ -518,7 +520,6 @@ static std::optional<DocumentationSymbol> getMetatableDocumentation(
const AstName& index
)
{
LUAU_ASSERT(FFlag::LuauDocumentationAtPosition);
auto indexIt = mtable->props.find("__index");
if (indexIt == mtable->props.end())
return std::nullopt;
@ -573,30 +574,11 @@ std::optional<DocumentationSymbol> getDocumentationSymbolAtPosition(const Source
return checkOverloadedDocumentationSymbol(module, propIt->second.type(), parentExpr, propIt->second.documentationSymbol);
}
}
else if (const ClassType* ctv = get<ClassType>(parentTy))
else if (const ExternType* etv = get<ExternType>(parentTy))
{
if (FFlag::LuauDocumentationAtPosition)
while (etv)
{
while (ctv)
{
if (auto propIt = ctv->props.find(indexName->index.value); propIt != ctv->props.end())
{
if (FFlag::LuauSolverV2)
{
if (auto ty = propIt->second.readTy)
return checkOverloadedDocumentationSymbol(module, *ty, parentExpr, propIt->second.documentationSymbol);
}
else
return checkOverloadedDocumentationSymbol(
module, propIt->second.type(), parentExpr, propIt->second.documentationSymbol
);
}
ctv = ctv->parent ? Luau::get<Luau::ClassType>(*ctv->parent) : nullptr;
}
}
else
{
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)
{
@ -608,17 +590,15 @@ std::optional<DocumentationSymbol> getDocumentationSymbolAtPosition(const Source
module, propIt->second.type(), parentExpr, propIt->second.documentationSymbol
);
}
etv = etv->parent ? Luau::get<Luau::ExternType>(*etv->parent) : nullptr;
}
}
else if (FFlag::LuauDocumentationAtPosition)
else if (const PrimitiveType* ptv = get<PrimitiveType>(parentTy); ptv && ptv->metatable)
{
if (const PrimitiveType* ptv = get<PrimitiveType>(parentTy); ptv && ptv->metatable)
if (auto mtable = get<TableType>(*ptv->metatable))
{
if (auto mtable = get<TableType>(*ptv->metatable))
{
if (std::optional<std::string> docSymbol = getMetatableDocumentation(module, parentExpr, mtable, indexName->index))
return docSymbol;
}
if (std::optional<std::string> docSymbol = getMetatableDocumentation(module, parentExpr, mtable, indexName->index))
return docSymbol;
}
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,27 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "Luau/AutocompleteTypes.h"
namespace Luau
{
struct Module;
struct FileResolver;
using ModulePtr = std::shared_ptr<Module>;
using ModuleName = std::string;
AutocompleteResult autocomplete_(
const ModulePtr& module,
NotNull<BuiltinTypes> builtinTypes,
TypeArena* typeArena,
std::vector<AstNode*>& ancestry,
Scope* globalScope,
const ScopePtr& scopeAtPosition,
Position position,
FileResolver* fileResolver,
StringCompletionCallback callback
);
} // namespace Luau

View file

@ -3,22 +3,23 @@
#include "Luau/Ast.h"
#include "Luau/Clone.h"
#include "Luau/Common.h"
#include "Luau/ConstraintGenerator.h"
#include "Luau/ConstraintSolver.h"
#include "Luau/DenseHash.h"
#include "Luau/Error.h"
#include "Luau/Frontend.h"
#include "Luau/Symbol.h"
#include "Luau/Common.h"
#include "Luau/ToString.h"
#include "Luau/ConstraintSolver.h"
#include "Luau/ConstraintGenerator.h"
#include "Luau/InferPolarity.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/TypeFunction.h"
#include "Luau/TypeInfer.h"
#include "Luau/TypePack.h"
#include "Luau/Type.h"
#include "Luau/TypeUtils.h"
#include "Luau/Subtyping.h"
#include <algorithm>
@ -29,51 +30,92 @@
*/
LUAU_FASTFLAG(LuauSolverV2)
LUAU_DYNAMIC_FASTINT(LuauTypeSolverRelease)
LUAU_FASTFLAGVARIABLE(LuauTypestateBuiltins2)
LUAU_FASTFLAGVARIABLE(LuauStringFormatArityFix)
LUAU_FASTFLAG(AutocompleteRequirePathSuggestions)
LUAU_FASTFLAG(LuauNonReentrantGeneralization2)
LUAU_FASTFLAGVARIABLE(LuauTableCloneClonesType3)
LUAU_FASTFLAGVARIABLE(LuauUserTypeFunTypecheck)
LUAU_FASTFLAGVARIABLE(LuauMagicFreezeCheckBlocked)
LUAU_FASTFLAGVARIABLE(LuauFormatUseLastPosition)
namespace Luau
{
static std::optional<WithPredicate<TypePackId>> magicFunctionSelect(
TypeChecker& typechecker,
const ScopePtr& scope,
const AstExprCall& expr,
WithPredicate<TypePackId> withPredicate
);
static std::optional<WithPredicate<TypePackId>> magicFunctionSetMetaTable(
TypeChecker& typechecker,
const ScopePtr& scope,
const AstExprCall& expr,
WithPredicate<TypePackId> withPredicate
);
static std::optional<WithPredicate<TypePackId>> magicFunctionAssert(
TypeChecker& typechecker,
const ScopePtr& scope,
const AstExprCall& expr,
WithPredicate<TypePackId> withPredicate
);
static std::optional<WithPredicate<TypePackId>> magicFunctionPack(
TypeChecker& typechecker,
const ScopePtr& scope,
const AstExprCall& expr,
WithPredicate<TypePackId> withPredicate
);
static std::optional<WithPredicate<TypePackId>> magicFunctionRequire(
TypeChecker& typechecker,
const ScopePtr& scope,
const AstExprCall& expr,
WithPredicate<TypePackId> withPredicate
);
struct MagicSelect final : MagicFunction
{
std::optional<WithPredicate<TypePackId>>
handleOldSolver(struct TypeChecker&, const std::shared_ptr<struct Scope>&, const class AstExprCall&, WithPredicate<TypePackId>) override;
bool infer(const MagicFunctionCallContext& ctx) override;
};
struct MagicSetMetatable final : MagicFunction
{
std::optional<WithPredicate<TypePackId>>
handleOldSolver(struct TypeChecker&, const std::shared_ptr<struct Scope>&, const class AstExprCall&, WithPredicate<TypePackId>) override;
bool infer(const MagicFunctionCallContext& ctx) override;
};
static bool dcrMagicFunctionSelect(MagicFunctionCallContext context);
static bool dcrMagicFunctionRequire(MagicFunctionCallContext context);
static bool dcrMagicFunctionPack(MagicFunctionCallContext context);
static bool dcrMagicFunctionFreeze(MagicFunctionCallContext context);
struct MagicAssert final : MagicFunction
{
std::optional<WithPredicate<TypePackId>>
handleOldSolver(struct TypeChecker&, const std::shared_ptr<struct Scope>&, const class AstExprCall&, WithPredicate<TypePackId>) override;
bool infer(const MagicFunctionCallContext& ctx) override;
};
struct MagicPack final : MagicFunction
{
std::optional<WithPredicate<TypePackId>>
handleOldSolver(struct TypeChecker&, const std::shared_ptr<struct Scope>&, const class AstExprCall&, WithPredicate<TypePackId>) override;
bool infer(const MagicFunctionCallContext& ctx) override;
};
struct MagicRequire final : MagicFunction
{
std::optional<WithPredicate<TypePackId>>
handleOldSolver(struct TypeChecker&, const std::shared_ptr<struct Scope>&, const class AstExprCall&, WithPredicate<TypePackId>) override;
bool infer(const MagicFunctionCallContext& ctx) override;
};
struct MagicClone final : MagicFunction
{
std::optional<WithPredicate<TypePackId>>
handleOldSolver(struct TypeChecker&, const std::shared_ptr<struct Scope>&, const class AstExprCall&, WithPredicate<TypePackId>) override;
bool infer(const MagicFunctionCallContext& ctx) override;
};
struct MagicFreeze final : MagicFunction
{
std::optional<WithPredicate<TypePackId>>
handleOldSolver(struct TypeChecker&, const std::shared_ptr<struct Scope>&, const class AstExprCall&, WithPredicate<TypePackId>) override;
bool infer(const MagicFunctionCallContext& ctx) override;
};
struct MagicFormat final : MagicFunction
{
std::optional<WithPredicate<TypePackId>>
handleOldSolver(struct TypeChecker&, const std::shared_ptr<struct Scope>&, const class AstExprCall&, WithPredicate<TypePackId>) override;
bool infer(const MagicFunctionCallContext& ctx) override;
bool typeCheck(const MagicFunctionTypeCheckContext& ctx) override;
};
struct MagicMatch final : MagicFunction
{
std::optional<WithPredicate<TypePackId>>
handleOldSolver(struct TypeChecker&, const std::shared_ptr<struct Scope>&, const class AstExprCall&, WithPredicate<TypePackId>) override;
bool infer(const MagicFunctionCallContext& ctx) override;
};
struct MagicGmatch final : MagicFunction
{
std::optional<WithPredicate<TypePackId>>
handleOldSolver(struct TypeChecker&, const std::shared_ptr<struct Scope>&, const class AstExprCall&, WithPredicate<TypePackId>) override;
bool infer(const MagicFunctionCallContext& ctx) override;
};
struct MagicFind final : MagicFunction
{
std::optional<WithPredicate<TypePackId>>
handleOldSolver(struct TypeChecker&, const std::shared_ptr<struct Scope>&, const class AstExprCall&, WithPredicate<TypePackId>) override;
bool infer(const MagicFunctionCallContext& ctx) override;
};
TypeId makeUnion(TypeArena& arena, std::vector<TypeId>&& types)
{
@ -168,34 +210,10 @@ TypeId makeFunction(
return arena.addType(std::move(ftv));
}
void attachMagicFunction(TypeId ty, MagicFunction fn)
void attachMagicFunction(TypeId ty, std::shared_ptr<MagicFunction> magic)
{
if (auto ftv = getMutable<FunctionType>(ty))
ftv->magicFunction = fn;
else
LUAU_ASSERT(!"Got a non functional type");
}
void attachDcrMagicFunction(TypeId ty, DcrMagicFunction fn)
{
if (auto ftv = getMutable<FunctionType>(ty))
ftv->dcrMagicFunction = fn;
else
LUAU_ASSERT(!"Got a non functional type");
}
void attachDcrMagicRefinement(TypeId ty, DcrMagicRefinement fn)
{
if (auto ftv = getMutable<FunctionType>(ty))
ftv->dcrMagicRefinement = fn;
else
LUAU_ASSERT(!"Got a non functional type");
}
void attachDcrMagicFunctionTypeCheck(TypeId ty, DcrMagicFunctionTypeCheck fn)
{
if (auto ftv = getMutable<FunctionType>(ty))
ftv->dcrMagicTypeCheck = fn;
ftv->magic = std::move(magic);
else
LUAU_ASSERT(!"Got a non functional type");
}
@ -230,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)
{
inferGenericPolarities(NotNull{&globals.globalTypes}, NotNull{scope.get()}, binding.typeId);
scope->bindings[globals.globalNames.names->getOrAdd(name.c_str())] = binding;
}
@ -271,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)
{
LUAU_ASSERT(!globals.globalTypes.types.isFrozen());
@ -278,6 +313,9 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
TypeArena& arena = globals.globalTypes;
NotNull<BuiltinTypes> builtinTypes = globals.builtinTypes;
Scope* globalScope = nullptr; // NotNull<Scope> when removing FFlag::LuauNonReentrantGeneralization2
if (FFlag::LuauNonReentrantGeneralization2)
globalScope = globals.globalScope.get();
if (FFlag::LuauSolverV2)
builtinTypeFunctions().addToScope(NotNull{&arena}, NotNull{globals.globalScope.get()});
@ -287,8 +325,8 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
);
LUAU_ASSERT(loadResult.success);
TypeId genericK = arena.addType(GenericType{"K"});
TypeId genericV = arena.addType(GenericType{"V"});
TypeId genericK = arena.addType(GenericType{globalScope, "K"});
TypeId genericV = arena.addType(GenericType{globalScope, "V"});
TypeId mapOfKtoV = arena.addType(TableType{{}, TableIndexer(genericK, genericV), globals.globalScope->level, TableState::Generic});
std::optional<TypeId> stringMetatableTy = getMetatable(builtinTypes->stringType, builtinTypes);
@ -301,6 +339,28 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
addGlobalBinding(globals, "string", it->second.type(), "@luau");
// Setup 'vector' metatable
if (auto it = globals.globalScope->exportedTypeBindings.find("vector"); it != globals.globalScope->exportedTypeBindings.end())
{
TypeId vectorTy = it->second.type;
ExternType* vectorCls = getMutable<ExternType>(vectorTy);
vectorCls->metatable = arena.addType(TableType{{}, std::nullopt, TypeLevel{}, TableState::Sealed});
TableType* metatableTy = Luau::getMutable<TableType>(vectorCls->metatable);
metatableTy->props["__add"] = {makeFunction(arena, vectorTy, {vectorTy}, {vectorTy})};
metatableTy->props["__sub"] = {makeFunction(arena, vectorTy, {vectorTy}, {vectorTy})};
metatableTy->props["__unm"] = {makeFunction(arena, vectorTy, {}, {vectorTy})};
std::initializer_list<TypeId> mulOverloads{
makeFunction(arena, vectorTy, {vectorTy}, {vectorTy}),
makeFunction(arena, vectorTy, {builtinTypes->numberType}, {vectorTy}),
};
metatableTy->props["__mul"] = {makeIntersection(arena, mulOverloads)};
metatableTy->props["__div"] = {makeIntersection(arena, mulOverloads)};
metatableTy->props["__idiv"] = {makeIntersection(arena, mulOverloads)};
}
// next<K, V>(t: Table<K, V>, i: K?) -> (K?, V)
TypePackId nextArgsTypePack = arena.addTypePack(TypePack{{mapOfKtoV, makeOption(builtinTypes, arena, genericK)}});
TypePackId nextRetsTypePack = arena.addTypePack(TypePack{{makeOption(builtinTypes, arena, genericK), genericV}});
@ -314,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)
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};
TypeId tabTy = arena.addType(tab);
@ -326,7 +386,7 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
if (FFlag::LuauSolverV2)
{
TypeId genericT = arena.addType(GenericType{"T"});
TypeId genericT = arena.addType(GenericType{globalScope, "T"});
TypeId tMetaMT = arena.addType(MetatableType{genericT, genericMT});
// clang-format off
@ -360,23 +420,30 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
// clang-format on
}
for (const auto& pair : globals.globalScope->bindings)
if (FFlag::LuauUserTypeFunTypecheck)
{
persist(pair.second.typeId);
if (TableType* ttv = getMutable<TableType>(pair.second.typeId))
finalizeGlobalBindings(globals.globalScope);
}
else
{
for (const auto& pair : globals.globalScope->bindings)
{
if (!ttv->name)
ttv->name = "typeof(" + toString(pair.first) + ")";
persist(pair.second.typeId);
if (TableType* ttv = getMutable<TableType>(pair.second.typeId))
{
if (!ttv->name)
ttv->name = "typeof(" + toString(pair.first) + ")";
}
}
}
attachMagicFunction(getGlobalBinding(globals, "assert"), magicFunctionAssert);
attachMagicFunction(getGlobalBinding(globals, "assert"), std::make_shared<MagicAssert>());
if (FFlag::LuauSolverV2)
{
// 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{
NotNull{&builtinTypeFunctions().intersectFunc}, {genericT, arena.addType(NegationType{builtinTypes->falsyType})}, {}
});
@ -387,9 +454,8 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
addGlobalBinding(globals, "assert", assertTy, "@luau");
}
attachMagicFunction(getGlobalBinding(globals, "setmetatable"), magicFunctionSetMetaTable);
attachMagicFunction(getGlobalBinding(globals, "select"), magicFunctionSelect);
attachDcrMagicFunction(getGlobalBinding(globals, "select"), dcrMagicFunctionSelect);
attachMagicFunction(getGlobalBinding(globals, "setmetatable"), std::make_shared<MagicSetMetatable>());
attachMagicFunction(getGlobalBinding(globals, "select"), std::make_shared<MagicSelect>());
if (TableType* ttv = getMutable<TableType>(getGlobalBinding(globals, "table")))
{
@ -400,12 +466,16 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
// the top table type. We do the best we can by modelling these
// functions using unconstrained generics. It's not quite right,
// 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});
TypeId idTyWithMagic = arena.addType(FunctionType{{genericTy}, {}, thePack, thePack});
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});
if (globalScope)
inferGenericPolarities(NotNull{&globals.globalTypes}, NotNull{globalScope}, idTy);
ttv->props["clone"] = makeProperty(idTy, "@luau/global/table.clone");
}
else
@ -420,23 +490,67 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
ttv->props["foreach"].deprecated = true;
ttv->props["foreachi"].deprecated = true;
attachMagicFunction(ttv->props["pack"].type(), magicFunctionPack);
attachDcrMagicFunction(ttv->props["pack"].type(), dcrMagicFunctionPack);
if (FFlag::LuauTypestateBuiltins2)
attachDcrMagicFunction(ttv->props["freeze"].type(), dcrMagicFunctionFreeze);
attachMagicFunction(ttv->props["pack"].type(), std::make_shared<MagicPack>());
if (FFlag::LuauTableCloneClonesType3)
attachMagicFunction(ttv->props["clone"].type(), std::make_shared<MagicClone>());
attachMagicFunction(ttv->props["freeze"].type(), std::make_shared<MagicFreeze>());
}
if (FFlag::AutocompleteRequirePathSuggestions)
TypeId requireTy = getGlobalBinding(globals, "require");
attachTag(requireTy, kRequireTagName);
attachMagicFunction(requireTy, std::make_shared<MagicRequire>());
if (FFlag::LuauUserTypeFunTypecheck)
{
TypeId requireTy = getGlobalBinding(globals, "require");
attachTag(requireTy, kRequireTagName);
attachMagicFunction(requireTy, magicFunctionRequire);
attachDcrMagicFunction(requireTy, dcrMagicFunctionRequire);
}
else
{
attachMagicFunction(getGlobalBinding(globals, "require"), magicFunctionRequire);
attachDcrMagicFunction(getGlobalBinding(globals, "require"), dcrMagicFunctionRequire);
// 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);
}
}
@ -476,7 +590,7 @@ static std::vector<TypeId> parseFormatString(NotNull<BuiltinTypes> builtinTypes,
return result;
}
std::optional<WithPredicate<TypePackId>> magicFunctionFormat(
std::optional<WithPredicate<TypePackId>> MagicFormat::handleOldSolver(
TypeChecker& typechecker,
const ScopePtr& scope,
const AstExprCall& expr,
@ -526,7 +640,7 @@ std::optional<WithPredicate<TypePackId>> magicFunctionFormat(
return WithPredicate<TypePackId>{arena.addTypePack({typechecker.stringType})};
}
static bool dcrMagicFunctionFormat(MagicFunctionCallContext context)
bool MagicFormat::infer(const MagicFunctionCallContext& context)
{
TypeArena* arena = context.solver->arena;
@ -570,7 +684,7 @@ static bool dcrMagicFunctionFormat(MagicFunctionCallContext context)
return true;
}
static void dcrMagicFunctionTypeCheckFormat(MagicFunctionTypeCheckContext context)
bool MagicFormat::typeCheck(const MagicFunctionTypeCheckContext& context)
{
AstExprConstantString* fmt = nullptr;
if (auto index = context.callSite->func->as<AstExprIndexName>(); index && context.callSite->self)
@ -586,11 +700,18 @@ static void dcrMagicFunctionTypeCheckFormat(MagicFunctionTypeCheckContext contex
if (!fmt)
{
if (FFlag::LuauStringFormatArityFix)
context.typechecker->reportError(CountMismatch{1, std::nullopt, 0, CountMismatch::Arg, true, "string.format"}, context.callSite->location);
return;
context.typechecker->reportError(CountMismatch{1, std::nullopt, 0, CountMismatch::Arg, true, "string.format"}, context.callSite->location);
return true;
}
// CLI-150726: The block below effectively constructs a type pack and then type checks it by going parameter-by-parameter.
// This does _not_ handle cases like:
//
// local foo : () -> (...string) = (nil :: any)
// print(string.format("%s %d %s", foo()))
//
// ... which should be disallowed.
std::vector<TypeId> expected = parseFormatString(context.builtinTypes, fmt->value.data, fmt->value.size);
const auto& [params, tail] = flatten(context.arguments);
@ -602,15 +723,30 @@ static void dcrMagicFunctionTypeCheckFormat(MagicFunctionTypeCheckContext contex
{
TypeId actualTy = params[i + paramOffset];
TypeId expectedTy = expected[i];
Location location = context.callSite->args.data[i + (calledWithSelf ? 0 : paramOffset)]->location;
Location location = FFlag::LuauFormatUseLastPosition
? context.callSite->args.data[std::min(context.callSite->args.size - 1, i + (calledWithSelf ? 0 : paramOffset))]->location
: context.callSite->args.data[i + (calledWithSelf ? 0 : paramOffset)]->location;
// use subtyping instead here
SubtypingResult result = context.typechecker->subtyping->isSubtype(actualTy, expectedTy, context.checkScope);
if (!result.isSubtype)
{
Reasonings reasonings = context.typechecker->explainReasonings(actualTy, expectedTy, location, result);
context.typechecker->reportError(TypeMismatch{expectedTy, actualTy, reasonings.toString()}, location);
switch (shouldSuppressErrors(NotNull{&context.typechecker->normalizer}, actualTy))
{
case ErrorSuppression::Suppress:
break;
case ErrorSuppression::NormalizationFailed:
break;
case ErrorSuppression::DoNotSuppress:
Reasonings reasonings = context.typechecker->explainReasonings(actualTy, expectedTy, location, result);
if (!reasonings.suppressed)
context.typechecker->reportError(TypeMismatch{expectedTy, actualTy, reasonings.toString()}, location);
}
}
}
return true;
}
static std::vector<TypeId> parsePatternString(NotNull<BuiltinTypes> builtinTypes, const char* data, size_t size)
@ -673,7 +809,7 @@ static std::vector<TypeId> parsePatternString(NotNull<BuiltinTypes> builtinTypes
return result;
}
static std::optional<WithPredicate<TypePackId>> magicFunctionGmatch(
std::optional<WithPredicate<TypePackId>> MagicGmatch::handleOldSolver(
TypeChecker& typechecker,
const ScopePtr& scope,
const AstExprCall& expr,
@ -709,7 +845,7 @@ static std::optional<WithPredicate<TypePackId>> magicFunctionGmatch(
return WithPredicate<TypePackId>{arena.addTypePack({iteratorType})};
}
static bool dcrMagicFunctionGmatch(MagicFunctionCallContext context)
bool MagicGmatch::infer(const MagicFunctionCallContext& context)
{
const auto& [params, tail] = flatten(context.arguments);
@ -742,7 +878,7 @@ static bool dcrMagicFunctionGmatch(MagicFunctionCallContext context)
return true;
}
static std::optional<WithPredicate<TypePackId>> magicFunctionMatch(
std::optional<WithPredicate<TypePackId>> MagicMatch::handleOldSolver(
TypeChecker& typechecker,
const ScopePtr& scope,
const AstExprCall& expr,
@ -782,7 +918,7 @@ static std::optional<WithPredicate<TypePackId>> magicFunctionMatch(
return WithPredicate<TypePackId>{returnList};
}
static bool dcrMagicFunctionMatch(MagicFunctionCallContext context)
bool MagicMatch::infer(const MagicFunctionCallContext& context)
{
const auto& [params, tail] = flatten(context.arguments);
@ -818,7 +954,7 @@ static bool dcrMagicFunctionMatch(MagicFunctionCallContext context)
return true;
}
static std::optional<WithPredicate<TypePackId>> magicFunctionFind(
std::optional<WithPredicate<TypePackId>> MagicFind::handleOldSolver(
TypeChecker& typechecker,
const ScopePtr& scope,
const AstExprCall& expr,
@ -876,7 +1012,7 @@ static std::optional<WithPredicate<TypePackId>> magicFunctionFind(
return WithPredicate<TypePackId>{returnList};
}
static bool dcrMagicFunctionFind(MagicFunctionCallContext context)
bool MagicFind::infer(const MagicFunctionCallContext& context)
{
const auto& [params, tail] = flatten(context.arguments);
@ -953,11 +1089,9 @@ TypeId makeStringMetatable(NotNull<BuiltinTypes> builtinTypes)
FunctionType formatFTV{arena->addTypePack(TypePack{{stringType}, variadicTailPack}), oneStringPack};
formatFTV.magicFunction = &magicFunctionFormat;
formatFTV.isCheckedFunction = true;
const TypeId formatFn = arena->addType(formatFTV);
attachDcrMagicFunction(formatFn, dcrMagicFunctionFormat);
attachDcrMagicFunctionTypeCheck(formatFn, dcrMagicFunctionTypeCheckFormat);
attachMagicFunction(formatFn, std::make_shared<MagicFormat>());
const TypeId stringToStringType = makeFunction(*arena, std::nullopt, {}, {}, {stringType}, {}, {stringType}, /* checked */ true);
@ -971,16 +1105,14 @@ TypeId makeStringMetatable(NotNull<BuiltinTypes> builtinTypes)
makeFunction(*arena, stringType, {}, {}, {stringType, replArgType, optionalNumber}, {}, {stringType, numberType}, /* checked */ false);
const TypeId gmatchFunc =
makeFunction(*arena, stringType, {}, {}, {stringType}, {}, {arena->addType(FunctionType{emptyPack, stringVariadicList})}, /* checked */ true);
attachMagicFunction(gmatchFunc, magicFunctionGmatch);
attachDcrMagicFunction(gmatchFunc, dcrMagicFunctionGmatch);
attachMagicFunction(gmatchFunc, std::make_shared<MagicGmatch>());
FunctionType matchFuncTy{
arena->addTypePack({stringType, stringType, optionalNumber}), arena->addTypePack(TypePackVar{VariadicTypePack{stringType}})
};
matchFuncTy.isCheckedFunction = true;
const TypeId matchFunc = arena->addType(matchFuncTy);
attachMagicFunction(matchFunc, magicFunctionMatch);
attachDcrMagicFunction(matchFunc, dcrMagicFunctionMatch);
attachMagicFunction(matchFunc, std::make_shared<MagicMatch>());
FunctionType findFuncTy{
arena->addTypePack({stringType, stringType, optionalNumber, optionalBoolean}),
@ -988,8 +1120,7 @@ TypeId makeStringMetatable(NotNull<BuiltinTypes> builtinTypes)
};
findFuncTy.isCheckedFunction = true;
const TypeId findFunc = arena->addType(findFuncTy);
attachMagicFunction(findFunc, magicFunctionFind);
attachDcrMagicFunction(findFunc, dcrMagicFunctionFind);
attachMagicFunction(findFunc, std::make_shared<MagicFind>());
// string.byte : string -> number? -> number? -> ...number
FunctionType stringDotByte{arena->addTypePack({stringType, optionalNumber, optionalNumber}), numberVariadicList};
@ -1050,7 +1181,7 @@ TypeId makeStringMetatable(NotNull<BuiltinTypes> builtinTypes)
return arena->addType(TableType{{{{"__index", {tableType}}}}, std::nullopt, TypeLevel{}, TableState::Sealed});
}
static std::optional<WithPredicate<TypePackId>> magicFunctionSelect(
std::optional<WithPredicate<TypePackId>> MagicSelect::handleOldSolver(
TypeChecker& typechecker,
const ScopePtr& scope,
const AstExprCall& expr,
@ -1095,7 +1226,7 @@ static std::optional<WithPredicate<TypePackId>> magicFunctionSelect(
return std::nullopt;
}
static bool dcrMagicFunctionSelect(MagicFunctionCallContext context)
bool MagicSelect::infer(const MagicFunctionCallContext& context)
{
if (context.callSite->args.size <= 0)
{
@ -1140,7 +1271,7 @@ static bool dcrMagicFunctionSelect(MagicFunctionCallContext context)
return false;
}
static std::optional<WithPredicate<TypePackId>> magicFunctionSetMetaTable(
std::optional<WithPredicate<TypePackId>> MagicSetMetatable::handleOldSolver(
TypeChecker& typechecker,
const ScopePtr& scope,
const AstExprCall& expr,
@ -1222,7 +1353,12 @@ static std::optional<WithPredicate<TypePackId>> magicFunctionSetMetaTable(
return WithPredicate<TypePackId>{arena.addTypePack({target})};
}
static std::optional<WithPredicate<TypePackId>> magicFunctionAssert(
bool MagicSetMetatable::infer(const MagicFunctionCallContext&)
{
return false;
}
std::optional<WithPredicate<TypePackId>> MagicAssert::handleOldSolver(
TypeChecker& typechecker,
const ScopePtr& scope,
const AstExprCall& expr,
@ -1256,7 +1392,12 @@ static std::optional<WithPredicate<TypePackId>> magicFunctionAssert(
return WithPredicate<TypePackId>{arena.addTypePack(TypePack{std::move(head), tail})};
}
static std::optional<WithPredicate<TypePackId>> magicFunctionPack(
bool MagicAssert::infer(const MagicFunctionCallContext&)
{
return false;
}
std::optional<WithPredicate<TypePackId>> MagicPack::handleOldSolver(
TypeChecker& typechecker,
const ScopePtr& scope,
const AstExprCall& expr,
@ -1299,7 +1440,7 @@ static std::optional<WithPredicate<TypePackId>> magicFunctionPack(
return WithPredicate<TypePackId>{arena.addTypePack({packedTable})};
}
static bool dcrMagicFunctionPack(MagicFunctionCallContext context)
bool MagicPack::infer(const MagicFunctionCallContext& context)
{
TypeArena* arena = context.solver->arena;
@ -1339,10 +1480,76 @@ static bool dcrMagicFunctionPack(MagicFunctionCallContext context)
return true;
}
static std::optional<TypeId> freezeTable(TypeId inputType, MagicFunctionCallContext& context)
std::optional<WithPredicate<TypePackId>> MagicClone::handleOldSolver(
TypeChecker& typechecker,
const ScopePtr& scope,
const AstExprCall& expr,
WithPredicate<TypePackId> withPredicate
)
{
LUAU_ASSERT(FFlag::LuauTableCloneClonesType3);
auto [paramPack, _predicates] = withPredicate;
TypeArena& arena = typechecker.currentModule->internalTypes;
const auto& [paramTypes, paramTail] = flatten(paramPack);
if (paramTypes.empty() || expr.args.size == 0)
{
typechecker.reportError(expr.argLocation, CountMismatch{1, std::nullopt, 0});
return std::nullopt;
}
TypeId inputType = follow(paramTypes[0]);
if (!get<TableType>(inputType))
return std::nullopt;
CloneState cloneState{typechecker.builtinTypes};
TypeId resultType = shallowClone(inputType, arena, cloneState);
TypePackId clonedTypePack = arena.addTypePack({resultType});
return WithPredicate<TypePackId>{clonedTypePack};
}
bool MagicClone::infer(const MagicFunctionCallContext& context)
{
LUAU_ASSERT(FFlag::LuauTableCloneClonesType3);
TypeArena* arena = context.solver->arena;
const auto& [paramTypes, paramTail] = flatten(context.arguments);
if (paramTypes.empty() || context.callSite->args.size == 0)
{
context.solver->reportError(CountMismatch{1, std::nullopt, 0}, context.callSite->argLocation);
return false;
}
TypeId inputType = follow(paramTypes[0]);
if (!get<TableType>(inputType))
return false;
CloneState cloneState{context.solver->builtinTypes};
TypeId resultType = shallowClone(inputType, *arena, cloneState, /* ignorePersistent */ true);
if (auto tableType = getMutable<TableType>(resultType))
{
tableType->scope = context.constraint->scope.get();
}
trackInteriorFreeType(context.constraint->scope.get(), resultType);
TypePackId clonedTypePack = arena->addTypePack({resultType});
asMutable(context.result)->ty.emplace<BoundTypePack>(clonedTypePack);
return true;
}
static std::optional<TypeId> freezeTable(TypeId inputType, const MagicFunctionCallContext& context)
{
TypeArena* arena = context.solver->arena;
inputType = follow(inputType);
if (auto mt = get<MetatableType>(inputType))
{
std::optional<TypeId> frozenTable = freezeTable(mt->table, context);
@ -1359,7 +1566,7 @@ static std::optional<TypeId> freezeTable(TypeId inputType, MagicFunctionCallCont
{
// Clone the input type, this will become our final result type after we mutate it.
CloneState cloneState{context.solver->builtinTypes};
TypeId resultType = shallowClone(inputType, *arena, cloneState);
TypeId resultType = shallowClone(inputType, *arena, cloneState, /* ignorePersistent */ true);
auto tableTy = getMutable<TableType>(resultType);
// `clone` should not break this.
LUAU_ASSERT(tableTy);
@ -1384,10 +1591,14 @@ static std::optional<TypeId> freezeTable(TypeId inputType, MagicFunctionCallCont
return std::nullopt;
}
static bool dcrMagicFunctionFreeze(MagicFunctionCallContext context)
std::optional<WithPredicate<TypePackId>> MagicFreeze::
handleOldSolver(struct TypeChecker&, const std::shared_ptr<struct Scope>&, const class AstExprCall&, WithPredicate<TypePackId>)
{
LUAU_ASSERT(FFlag::LuauTypestateBuiltins2);
return std::nullopt;
}
bool MagicFreeze::infer(const MagicFunctionCallContext& context)
{
TypeArena* arena = context.solver->arena;
const DataFlowGraph* dfg = context.solver->dfg.get();
Scope* scope = context.constraint->scope.get();
@ -1405,6 +1616,17 @@ static bool dcrMagicFunctionFreeze(MagicFunctionCallContext context)
std::optional<DefId> resultDef = dfg->getDefOptional(targetExpr);
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);
if (!frozenType)
@ -1445,7 +1667,7 @@ static bool checkRequirePath(TypeChecker& typechecker, AstExpr* expr)
return good;
}
static std::optional<WithPredicate<TypePackId>> magicFunctionRequire(
std::optional<WithPredicate<TypePackId>> MagicRequire::handleOldSolver(
TypeChecker& typechecker,
const ScopePtr& scope,
const AstExprCall& expr,
@ -1491,7 +1713,7 @@ static bool checkRequirePathDcr(NotNull<ConstraintSolver> solver, AstExpr* expr)
return good;
}
static bool dcrMagicFunctionRequire(MagicFunctionCallContext context)
bool MagicRequire::infer(const MagicFunctionCallContext& context)
{
if (context.callSite->args.size != 1)
{

View file

@ -1,16 +1,20 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/Clone.h"
#include "Luau/Common.h"
#include "Luau/NotNull.h"
#include "Luau/Type.h"
#include "Luau/TypePack.h"
#include "Luau/Unifiable.h"
#include "Luau/VisitType.h"
LUAU_FASTFLAG(LuauSolverV2)
// 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_FASTFLAGVARIABLE(LuauClonedTableAndFunctionTypesMustHaveScopes)
LUAU_FASTFLAGVARIABLE(LuauDoNotClonePersistentBindings)
LUAU_FASTFLAG(LuauIncrementalAutocompleteDemandBasedCloning)
namespace Luau
{
@ -27,6 +31,8 @@ const T* get(const Kind& kind)
class TypeCloner
{
protected:
NotNull<TypeArena> arena;
NotNull<BuiltinTypes> builtinTypes;
@ -38,17 +44,31 @@ class TypeCloner
NotNull<SeenTypes> types;
NotNull<SeenTypePacks> packs;
TypeId forceTy = nullptr;
TypePackId forceTp = nullptr;
int steps = 0;
public:
TypeCloner(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, NotNull<SeenTypes> types, NotNull<SeenTypePacks> packs)
TypeCloner(
NotNull<TypeArena> arena,
NotNull<BuiltinTypes> builtinTypes,
NotNull<SeenTypes> types,
NotNull<SeenTypePacks> packs,
TypeId forceTy,
TypePackId forceTp
)
: arena(arena)
, builtinTypes(builtinTypes)
, types(types)
, packs(packs)
, forceTy(forceTy)
, forceTp(forceTp)
{
}
virtual ~TypeCloner() = default;
TypeId clone(TypeId ty)
{
shallowClone(ty);
@ -107,12 +127,13 @@ private:
}
}
protected:
std::optional<TypeId> find(TypeId ty) const
{
ty = follow(ty, FollowOption::DisableLazyTypeThunks);
if (auto it = types->find(ty); it != types->end())
return it->second;
else if (ty->persistent)
else if (ty->persistent && ty != forceTy)
return ty;
return std::nullopt;
}
@ -122,7 +143,7 @@ private:
tp = follow(tp);
if (auto it = packs->find(tp); it != packs->end())
return it->second;
else if (tp->persistent)
else if (tp->persistent && tp != forceTp)
return tp;
return std::nullopt;
}
@ -141,14 +162,14 @@ private:
}
public:
TypeId shallowClone(TypeId ty)
virtual TypeId shallowClone(TypeId ty)
{
// We want to [`Luau::follow`] but without forcing the expansion of [`LazyType`]s.
ty = follow(ty, FollowOption::DisableLazyTypeThunks);
if (auto clone = find(ty))
return *clone;
else if (ty->persistent)
else if (ty->persistent && ty != forceTy)
return ty;
TypeId target = arena->addType(ty->ty);
@ -158,8 +179,6 @@ public:
generic->scope = nullptr;
else if (auto free = getMutable<FreeType>(target))
free->scope = nullptr;
else if (auto fn = getMutable<FunctionType>(target))
fn->scope = nullptr;
else if (auto table = getMutable<TableType>(target))
table->scope = nullptr;
@ -168,13 +187,13 @@ public:
return target;
}
TypePackId shallowClone(TypePackId tp)
virtual TypePackId shallowClone(TypePackId tp)
{
tp = follow(tp);
if (auto clone = find(tp))
return *clone;
else if (tp->persistent)
else if (tp->persistent && tp != forceTp)
return tp;
TypePackId target = arena->addTypePack(tp->ty);
@ -257,8 +276,7 @@ private:
LUAU_ASSERT(!"Item holds neither TypeId nor TypePackId when enqueuing its children?");
}
// ErrorType and ErrorTypePack is an alias to this type.
void cloneChildren(Unifiable::Error* t)
void cloneChildren(ErrorType* t)
{
// noop.
}
@ -337,7 +355,7 @@ private:
t->metatable = shallowClone(t->metatable);
}
void cloneChildren(ClassType* t)
void cloneChildren(ExternType* t)
{
for (auto& [_, p] : t->props)
p = shallowClone(p);
@ -377,7 +395,7 @@ private:
ty = shallowClone(ty);
}
void cloneChildren(LazyType* t)
virtual void cloneChildren(LazyType* t)
{
if (auto unwrapped = t->unwrapped.load())
t->unwrapped.store(shallowClone(unwrapped));
@ -428,6 +446,11 @@ private:
t->boundTo = shallowClone(t->boundTo);
}
void cloneChildren(ErrorTypePack* t)
{
// noop.
}
void cloneChildren(VariadicTypePack* t)
{
t->ty = shallowClone(t->ty);
@ -452,23 +475,122 @@ private:
}
};
class FragmentAutocompleteTypeCloner final : public TypeCloner
{
Scope* replacementForNullScope = nullptr;
public:
FragmentAutocompleteTypeCloner(
NotNull<TypeArena> arena,
NotNull<BuiltinTypes> builtinTypes,
NotNull<SeenTypes> types,
NotNull<SeenTypePacks> packs,
TypeId forceTy,
TypePackId forceTp,
Scope* replacementForNullScope
)
: TypeCloner(arena, builtinTypes, types, packs, forceTy, forceTp)
, replacementForNullScope(replacementForNullScope)
{
LUAU_ASSERT(replacementForNullScope);
}
TypeId shallowClone(TypeId ty) override
{
// We want to [`Luau::follow`] but without forcing the expansion of [`LazyType`]s.
ty = follow(ty, FollowOption::DisableLazyTypeThunks);
if (auto clone = find(ty))
return *clone;
else if (ty->persistent && ty != forceTy)
return ty;
TypeId target = arena->addType(ty->ty);
asMutable(target)->documentationSymbol = ty->documentationSymbol;
if (auto generic = getMutable<GenericType>(target))
generic->scope = nullptr;
else if (auto free = getMutable<FreeType>(target))
{
free->scope = replacementForNullScope;
}
else if (auto tt = getMutable<TableType>(target))
{
if (FFlag::LuauClonedTableAndFunctionTypesMustHaveScopes)
tt->scope = replacementForNullScope;
}
(*types)[ty] = target;
queue.emplace_back(target);
return target;
}
TypePackId shallowClone(TypePackId tp) override
{
tp = follow(tp);
if (auto clone = find(tp))
return *clone;
else if (tp->persistent && tp != forceTp)
return tp;
TypePackId target = arena->addTypePack(tp->ty);
if (auto generic = getMutable<GenericTypePack>(target))
generic->scope = nullptr;
else if (auto free = getMutable<FreeTypePack>(target))
free->scope = replacementForNullScope;
(*packs)[tp] = target;
queue.emplace_back(target);
return target;
}
void cloneChildren(LazyType* t) override
{
// Do not clone lazy types
if (!FFlag::LuauIncrementalAutocompleteDemandBasedCloning)
{
if (auto unwrapped = t->unwrapped.load())
t->unwrapped.store(shallowClone(unwrapped));
}
}
};
} // namespace
TypePackId shallowClone(TypePackId tp, TypeArena& dest, CloneState& cloneState)
TypePackId shallowClone(TypePackId tp, TypeArena& dest, CloneState& cloneState, bool ignorePersistent)
{
if (tp->persistent)
if (tp->persistent && !ignorePersistent)
return tp;
TypeCloner cloner{NotNull{&dest}, cloneState.builtinTypes, NotNull{&cloneState.seenTypes}, NotNull{&cloneState.seenTypePacks}};
TypeCloner cloner{
NotNull{&dest},
cloneState.builtinTypes,
NotNull{&cloneState.seenTypes},
NotNull{&cloneState.seenTypePacks},
nullptr,
ignorePersistent ? tp : nullptr
};
return cloner.shallowClone(tp);
}
TypeId shallowClone(TypeId typeId, TypeArena& dest, CloneState& cloneState)
TypeId shallowClone(TypeId typeId, TypeArena& dest, CloneState& cloneState, bool ignorePersistent)
{
if (typeId->persistent)
if (typeId->persistent && !ignorePersistent)
return typeId;
TypeCloner cloner{NotNull{&dest}, cloneState.builtinTypes, NotNull{&cloneState.seenTypes}, NotNull{&cloneState.seenTypePacks}};
TypeCloner cloner{
NotNull{&dest},
cloneState.builtinTypes,
NotNull{&cloneState.seenTypes},
NotNull{&cloneState.seenTypePacks},
ignorePersistent ? typeId : nullptr,
nullptr
};
return cloner.shallowClone(typeId);
}
@ -477,7 +599,7 @@ TypePackId clone(TypePackId tp, TypeArena& dest, CloneState& cloneState)
if (tp->persistent)
return tp;
TypeCloner cloner{NotNull{&dest}, cloneState.builtinTypes, NotNull{&cloneState.seenTypes}, NotNull{&cloneState.seenTypePacks}};
TypeCloner cloner{NotNull{&dest}, cloneState.builtinTypes, NotNull{&cloneState.seenTypes}, NotNull{&cloneState.seenTypePacks}, nullptr, nullptr};
return cloner.clone(tp);
}
@ -486,13 +608,13 @@ TypeId clone(TypeId typeId, TypeArena& dest, CloneState& cloneState)
if (typeId->persistent)
return typeId;
TypeCloner cloner{NotNull{&dest}, cloneState.builtinTypes, NotNull{&cloneState.seenTypes}, NotNull{&cloneState.seenTypePacks}};
TypeCloner cloner{NotNull{&dest}, cloneState.builtinTypes, NotNull{&cloneState.seenTypes}, NotNull{&cloneState.seenTypePacks}, nullptr, nullptr};
return cloner.clone(typeId);
}
TypeFun clone(const TypeFun& typeFun, TypeArena& dest, CloneState& cloneState)
{
TypeCloner cloner{NotNull{&dest}, cloneState.builtinTypes, NotNull{&cloneState.seenTypes}, NotNull{&cloneState.seenTypePacks}};
TypeCloner cloner{NotNull{&dest}, cloneState.builtinTypes, NotNull{&cloneState.seenTypes}, NotNull{&cloneState.seenTypePacks}, nullptr, nullptr};
TypeFun copy = typeFun;
@ -517,4 +639,110 @@ TypeFun clone(const TypeFun& typeFun, TypeArena& dest, CloneState& cloneState)
return copy;
}
Binding clone(const Binding& binding, TypeArena& dest, CloneState& cloneState)
{
TypeCloner cloner{NotNull{&dest}, cloneState.builtinTypes, NotNull{&cloneState.seenTypes}, NotNull{&cloneState.seenTypePacks}, nullptr, nullptr};
Binding b;
b.deprecated = binding.deprecated;
b.deprecatedSuggestion = binding.deprecatedSuggestion;
b.documentationSymbol = binding.documentationSymbol;
b.location = binding.location;
b.typeId = cloner.clone(binding.typeId);
return b;
}
TypePackId cloneIncremental(TypePackId tp, TypeArena& dest, CloneState& cloneState, Scope* freshScopeForFreeTypes)
{
if (tp->persistent)
return tp;
FragmentAutocompleteTypeCloner cloner{
NotNull{&dest},
cloneState.builtinTypes,
NotNull{&cloneState.seenTypes},
NotNull{&cloneState.seenTypePacks},
nullptr,
nullptr,
freshScopeForFreeTypes
};
return cloner.clone(tp);
}
TypeId cloneIncremental(TypeId typeId, TypeArena& dest, CloneState& cloneState, Scope* freshScopeForFreeTypes)
{
if (typeId->persistent)
return typeId;
FragmentAutocompleteTypeCloner cloner{
NotNull{&dest},
cloneState.builtinTypes,
NotNull{&cloneState.seenTypes},
NotNull{&cloneState.seenTypePacks},
nullptr,
nullptr,
freshScopeForFreeTypes
};
return cloner.clone(typeId);
}
TypeFun cloneIncremental(const TypeFun& typeFun, TypeArena& dest, CloneState& cloneState, Scope* freshScopeForFreeTypes)
{
FragmentAutocompleteTypeCloner cloner{
NotNull{&dest},
cloneState.builtinTypes,
NotNull{&cloneState.seenTypes},
NotNull{&cloneState.seenTypePacks},
nullptr,
nullptr,
freshScopeForFreeTypes
};
TypeFun copy = typeFun;
for (auto& param : copy.typeParams)
{
param.ty = cloner.clone(param.ty);
if (param.defaultValue)
param.defaultValue = cloner.clone(*param.defaultValue);
}
for (auto& param : copy.typePackParams)
{
param.tp = cloner.clone(param.tp);
if (param.defaultValue)
param.defaultValue = cloner.clone(*param.defaultValue);
}
copy.type = cloner.clone(copy.type);
return copy;
}
Binding cloneIncremental(const Binding& binding, TypeArena& dest, CloneState& cloneState, Scope* freshScopeForFreeTypes)
{
FragmentAutocompleteTypeCloner cloner{
NotNull{&dest},
cloneState.builtinTypes,
NotNull{&cloneState.seenTypes},
NotNull{&cloneState.seenTypePacks},
nullptr,
nullptr,
freshScopeForFreeTypes
};
Binding b;
b.deprecated = binding.deprecated;
b.deprecatedSuggestion = binding.deprecatedSuggestion;
b.documentationSymbol = binding.documentationSymbol;
b.location = binding.location;
b.typeId = FFlag::LuauDoNotClonePersistentBindings && binding.typeId->persistent ? binding.typeId : cloner.clone(binding.typeId);
return b;
}
} // namespace Luau

View file

@ -3,6 +3,8 @@
#include "Luau/Constraint.h"
#include "Luau/VisitType.h"
LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
namespace Luau
{
@ -18,7 +20,7 @@ struct ReferenceCountInitializer : TypeOnceVisitor
DenseHashSet<TypeId>* result;
ReferenceCountInitializer(DenseHashSet<TypeId>* result)
explicit ReferenceCountInitializer(DenseHashSet<TypeId>* result)
: result(result)
{
}
@ -41,11 +43,16 @@ struct ReferenceCountInitializer : TypeOnceVisitor
return false;
}
bool visit(TypeId ty, const ClassType&) override
bool visit(TypeId ty, const ExternType&) override
{
// ClassTypes never contain free types.
// ExternTypes never contain free types.
return false;
}
bool visit(TypeId, const TypeFunctionInstanceType&) override
{
return FFlag::DebugLuauGreedyGeneralization;
}
};
bool isReferenceCountedType(const TypeId typ)
@ -97,6 +104,11 @@ DenseHashSet<TypeId> Constraint::getMaybeMutatedFreeTypes() const
{
rci.traverse(fchc->argsPack);
}
else if (auto fcc = get<FunctionCallConstraint>(*this); fcc && FFlag::DebugLuauGreedyGeneralization)
{
rci.traverse(fcc->fn);
rci.traverse(fcc->argsPack);
}
else if (auto ptc = get<PrimitiveTypeConstraint>(*this))
{
rci.traverse(ptc->freeType);
@ -104,12 +116,15 @@ DenseHashSet<TypeId> Constraint::getMaybeMutatedFreeTypes() const
else if (auto hpc = get<HasPropConstraint>(*this))
{
rci.traverse(hpc->resultType);
// `HasPropConstraints` should not mutate `subjectType`.
if (FFlag::DebugLuauGreedyGeneralization)
rci.traverse(hpc->subjectType);
}
else if (auto hic = get<HasIndexerConstraint>(*this))
{
if (FFlag::DebugLuauGreedyGeneralization)
rci.traverse(hic->subjectType);
rci.traverse(hic->resultType);
// `HasIndexerConstraint` should not mutate `subjectType` or `indexType`.
// `HasIndexerConstraint` should not mutate `indexType`.
}
else if (auto apc = get<AssignPropConstraint>(*this))
{
@ -128,10 +143,18 @@ DenseHashSet<TypeId> Constraint::getMaybeMutatedFreeTypes() const
rci.traverse(ty);
// `UnpackConstraint` should not mutate `sourcePack`.
}
else if (auto rpc = get<ReduceConstraint>(*this); FFlag::DebugLuauGreedyGeneralization && rpc)
{
rci.traverse(rpc->ty);
}
else if (auto rpc = get<ReducePackConstraint>(*this))
{
rci.traverse(rpc->tp);
}
else if (auto tcc = get<TableCheckConstraint>(*this))
{
rci.traverse(tcc->exprType);
}
return types;
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -13,33 +13,25 @@
LUAU_FASTFLAG(DebugLuauFreezeArena)
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauTypestateBuiltins2)
LUAU_FASTFLAGVARIABLE(LuauPreprocessTypestatedArgument)
LUAU_FASTFLAGVARIABLE(LuauDfgScopeStackTrueReset)
LUAU_FASTFLAGVARIABLE(LuauDfgScopeStackNotNull)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
LUAU_FASTFLAGVARIABLE(LuauDoNotAddUpvalueTypesToLocalType)
LUAU_FASTFLAGVARIABLE(LuauDfgIfBlocksShouldRespectControlFlow)
namespace Luau
{
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
{
ScopeStack& stack;
size_t previousSize;
PushScope(ScopeStack& stack, DfgScope* scope)
: stack(stack)
, previousSize(stack.size())
{
// `scope` should never be `nullptr` here.
LUAU_ASSERT(scope);
@ -48,7 +40,18 @@ struct PushScope
~PushScope()
{
stack.pop_back();
if (FFlag::LuauDfgScopeStackTrueReset)
{
// If somehow this stack has _shrunk_ to be smaller than we expect,
// something very strange has happened.
LUAU_ASSERT(stack.size() > previousSize);
while (stack.size() > previousSize)
stack.pop_back();
}
else
{
stack.pop_back();
}
}
};
@ -62,6 +65,12 @@ const RefinementKey* RefinementKeyArena::node(const RefinementKey* parent, DefId
return allocator.allocate(RefinementKey{parent, def, propName});
}
DataFlowGraph::DataFlowGraph(NotNull<DefArena> defArena, NotNull<RefinementKeyArena> keyArena)
: defArena{defArena}
, keyArena{keyArena}
{
}
DefId DataFlowGraph::getDef(const AstExpr* expr) const
{
auto def = astDefs.find(expr);
@ -77,12 +86,6 @@ std::optional<DefId> DataFlowGraph::getDefOptional(const AstExpr* expr) const
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
{
auto def = localDefs.find(local);
@ -178,41 +181,33 @@ bool DfgScope::canUpdateDefinition(DefId def, const std::string& key) const
return true;
}
DataFlowGraph DataFlowGraphBuilder::build(AstStatBlock* block, NotNull<InternalErrorReporter> handle)
DataFlowGraphBuilder::DataFlowGraphBuilder(NotNull<DefArena> defArena, NotNull<RefinementKeyArena> keyArena)
: graph{defArena, keyArena}
, defArena{defArena}
, keyArena{keyArena}
{
LUAU_TIMETRACE_SCOPE("DataFlowGraphBuilder::build", "Typechecking");
LUAU_ASSERT(FFlag::LuauSolverV2);
DataFlowGraphBuilder builder;
builder.handle = handle;
DfgScope* moduleScope = builder.makeChildScope(block->location);
PushScope ps{builder.scopeStack, moduleScope};
builder.visitBlockWithoutChildScope(block);
builder.resolveCaptures();
if (FFlag::DebugLuauFreezeArena)
{
builder.defArena->allocator.freeze();
builder.keyArena->allocator.freeze();
}
return std::move(builder.graph);
}
std::pair<std::shared_ptr<DataFlowGraph>, std::vector<std::unique_ptr<DfgScope>>> DataFlowGraphBuilder::buildShared(
DataFlowGraph DataFlowGraphBuilder::build(
AstStatBlock* block,
NotNull<InternalErrorReporter> handle
NotNull<DefArena> defArena,
NotNull<RefinementKeyArena> keyArena,
NotNull<struct InternalErrorReporter> handle
)
{
LUAU_TIMETRACE_SCOPE("DataFlowGraphBuilder::build", "Typechecking");
LUAU_ASSERT(FFlag::LuauSolverV2);
DataFlowGraphBuilder builder;
DataFlowGraphBuilder builder(defArena, keyArena);
builder.handle = handle;
DfgScope* moduleScope = builder.makeChildScope(block->location);
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};
builder.visitBlockWithoutChildScope(block);
builder.resolveCaptures();
@ -223,56 +218,6 @@ std::pair<std::shared_ptr<DataFlowGraph>, std::vector<std::unique_ptr<DfgScope>>
builder.keyArena->allocator.freeze();
}
return {std::make_shared<DataFlowGraph>(std::move(builder.graph)), std::move(builder.scopes)};
}
DataFlowGraph DataFlowGraphBuilder::updateGraph(
const DataFlowGraph& staleGraph,
const std::vector<std::unique_ptr<DfgScope>>& scopes,
AstStatBlock* fragment,
const Position& cursorPos,
NotNull<InternalErrorReporter> handle
)
{
LUAU_TIMETRACE_SCOPE("DataFlowGraphBuilder::build", "Typechecking");
LUAU_ASSERT(FFlag::LuauSolverV2);
DataFlowGraphBuilder builder;
builder.handle = handle;
// Generate a list of prepopulated locals
ReferencedDefFinder finder;
fragment->visit(&finder);
for (AstLocal* loc : finder.referencedLocalDefs)
{
if (staleGraph.localDefs.contains(loc))
{
builder.graph.localDefs[loc] = *staleGraph.localDefs.find(loc);
}
}
// Figure out which scope we should start re-accumulating DFG information from again
DfgScope* nearest = nullptr;
for (auto& sc : scopes)
{
if (nearest == nullptr || (sc->location.begin <= cursorPos && nearest->location.begin < sc->location.begin))
nearest = sc.get();
}
// The scope stack should start with the nearest enclosing scope so we can resume DFG'ing correctly
PushScope ps{builder.scopeStack, nearest};
// Conspire for the current scope in the scope stack to be a fresh dfg scope, parented to the above nearest enclosing scope, so any insertions are
// isolated there
DfgScope* scope = builder.makeChildScope(fragment->location);
PushScope psAgain{builder.scopeStack, scope};
builder.visitBlockWithoutChildScope(fragment);
if (FFlag::DebugLuauFreezeArena)
{
builder.defArena->allocator.freeze();
builder.keyArena->allocator.freeze();
}
return std::move(builder.graph);
}
@ -294,16 +239,25 @@ 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())
return nullptr; // nullptr is the root DFG scope.
return scopeStack.back();
}
DfgScope* DataFlowGraphBuilder::makeChildScope(Location loc, DfgScope::ScopeType scopeType)
DfgScope* DataFlowGraphBuilder::makeChildScope(DfgScope::ScopeType scopeType)
{
return scopes.emplace_back(new DfgScope{currentScope(), scopeType, loc}).get();
if (FFlag::LuauDfgScopeStackNotNull)
return scopes.emplace_back(new DfgScope{currentScope(), scopeType}).get();
else
return scopes.emplace_back(new DfgScope{currentScope_DEPRECATED(), scopeType}).get();
}
void DataFlowGraphBuilder::join(DfgScope* p, DfgScope* a, DfgScope* b)
@ -378,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.
bool outsideLoopScope = false;
@ -405,15 +359,15 @@ DefId DataFlowGraphBuilder::lookup(Symbol symbol)
}
}
DefId result = defArena->freshCell();
DefId result = defArena->freshCell(symbol, location);
scope->bindings[symbol] = result;
captures[symbol].allVersions.push_back(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)
{
if (auto props = current->props.find(def))
@ -423,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
{
DefId result = defArena->freshCell();
DefId result = defArena->freshCell(def->name, location);
scope->props[def][key] = result;
return result;
}
@ -433,7 +387,7 @@ DefId DataFlowGraphBuilder::lookup(DefId def, const std::string& key)
{
std::vector<DefId> defs;
for (DefId operand : phi->operands)
defs.push_back(lookup(operand, key));
defs.push_back(lookup(operand, key, location));
DefId result = defArena->phi(defs);
scope->props[def][key] = result;
@ -441,7 +395,7 @@ DefId DataFlowGraphBuilder::lookup(DefId def, const std::string& key)
}
else if (get<Cell>(def))
{
DefId result = defArena->freshCell();
DefId result = defArena->freshCell(def->name, location);
scope->props[def][key] = result;
return result;
}
@ -451,7 +405,7 @@ DefId DataFlowGraphBuilder::lookup(DefId def, const std::string& key)
ControlFlow DataFlowGraphBuilder::visit(AstStatBlock* b)
{
DfgScope* child = makeChildScope(b->location);
DfgScope* child = makeChildScope();
ControlFlow cf;
{
@ -459,7 +413,10 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatBlock* b)
cf = visitBlockWithoutChildScope(b);
}
currentScope()->inherit(child);
if (FFlag::LuauDfgScopeStackNotNull)
currentScope()->inherit(child);
else
currentScope_DEPRECATED()->inherit(child);
return cf;
}
@ -516,7 +473,7 @@ ControlFlow DataFlowGraphBuilder::visit(AstStat* s)
return visit(d);
else if (auto d = s->as<AstStatDeclareFunction>())
return visit(d);
else if (auto d = s->as<AstStatDeclareClass>())
else if (auto d = s->as<AstStatDeclareExternType>())
return visit(d);
else if (auto error = s->as<AstStatError>())
return visit(error);
@ -528,8 +485,8 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatIf* i)
{
visitExpr(i->condition);
DfgScope* thenScope = makeChildScope(i->thenbody->location);
DfgScope* elseScope = makeChildScope(i->elsebody ? i->elsebody->location : i->location);
DfgScope* thenScope = makeChildScope();
DfgScope* elseScope = makeChildScope();
ControlFlow thencf;
{
@ -544,13 +501,27 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatIf* i)
elsecf = visit(i->elsebody);
}
DfgScope* scope = currentScope();
if (thencf != ControlFlow::None && elsecf == ControlFlow::None)
join(scope, scope, elseScope);
else if (thencf == ControlFlow::None && elsecf != ControlFlow::None)
join(scope, thenScope, scope);
else if ((thencf | elsecf) == ControlFlow::None)
join(scope, thenScope, elseScope);
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)
join(scope, scope, elseScope);
else if (thencf == ControlFlow::None && elsecf != ControlFlow::None)
join(scope, thenScope, scope);
else if ((thencf | elsecf) == ControlFlow::None)
join(scope, thenScope, elseScope);
}
if (thencf == elsecf)
return thencf;
@ -563,7 +534,7 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatIf* i)
ControlFlow DataFlowGraphBuilder::visit(AstStatWhile* w)
{
// TODO(controlflow): entry point has a back edge from exit point
DfgScope* whileScope = makeChildScope(w->location, DfgScope::Loop);
DfgScope* whileScope = makeChildScope(DfgScope::Loop);
{
PushScope ps{scopeStack, whileScope};
@ -571,7 +542,10 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatWhile* w)
visit(w->body);
}
currentScope()->inherit(whileScope);
if (FFlag::LuauDfgScopeStackNotNull)
currentScope()->inherit(whileScope);
else
currentScope_DEPRECATED()->inherit(whileScope);
return ControlFlow::None;
}
@ -579,7 +553,7 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatWhile* w)
ControlFlow DataFlowGraphBuilder::visit(AstStatRepeat* r)
{
// TODO(controlflow): entry point has a back edge from exit point
DfgScope* repeatScope = makeChildScope(r->location, DfgScope::Loop);
DfgScope* repeatScope = makeChildScope(DfgScope::Loop);
{
PushScope ps{scopeStack, repeatScope};
@ -587,7 +561,10 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatRepeat* r)
visitExpr(r->condition);
}
currentScope()->inherit(repeatScope);
if (FFlag::LuauDfgScopeStackNotNull)
currentScope()->inherit(repeatScope);
else
currentScope_DEPRECATED()->inherit(repeatScope);
return ControlFlow::None;
}
@ -636,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
// make sure that the non-aliased defs are also marked as a subscript for refinements.
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)
{
AstExpr* e = l->values.data[i];
@ -646,7 +623,10 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatLocal* l)
}
}
graph.localDefs[local] = def;
currentScope()->bindings[local] = def;
if (FFlag::LuauDfgScopeStackNotNull)
currentScope()->bindings[local] = def;
else
currentScope_DEPRECATED()->bindings[local] = def;
captures[local].allVersions.push_back(def);
}
@ -655,7 +635,7 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatLocal* l)
ControlFlow DataFlowGraphBuilder::visit(AstStatFor* f)
{
DfgScope* forScope = makeChildScope(f->location, DfgScope::Loop);
DfgScope* forScope = makeChildScope(DfgScope::Loop);
visitExpr(f->from);
visitExpr(f->to);
@ -668,23 +648,29 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatFor* f)
if (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;
currentScope()->bindings[f->var] = def;
if (FFlag::LuauDfgScopeStackNotNull)
currentScope()->bindings[f->var] = def;
else
currentScope_DEPRECATED()->bindings[f->var] = def;
captures[f->var].allVersions.push_back(def);
// TODO(controlflow): entry point has a back edge from exit point
visit(f->body);
}
currentScope()->inherit(forScope);
if (FFlag::LuauDfgScopeStackNotNull)
currentScope()->inherit(forScope);
else
currentScope_DEPRECATED()->inherit(forScope);
return ControlFlow::None;
}
ControlFlow DataFlowGraphBuilder::visit(AstStatForIn* f)
{
DfgScope* forScope = makeChildScope(f->location, DfgScope::Loop);
DfgScope* forScope = makeChildScope(DfgScope::Loop);
{
PushScope ps{scopeStack, forScope};
@ -694,9 +680,12 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatForIn* f)
if (local->annotation)
visitType(local->annotation);
DefId def = defArena->freshCell();
DefId def = defArena->freshCell(local, local->location);
graph.localDefs[local] = def;
currentScope()->bindings[local] = def;
if (FFlag::LuauDfgScopeStackNotNull)
currentScope()->bindings[local] = def;
else
currentScope_DEPRECATED()->bindings[local] = def;
captures[local].allVersions.push_back(def);
}
@ -707,8 +696,10 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatForIn* f)
visit(f->body);
}
currentScope()->inherit(forScope);
if (FFlag::LuauDfgScopeStackNotNull)
currentScope()->inherit(forScope);
else
currentScope_DEPRECATED()->inherit(forScope);
return ControlFlow::None;
}
@ -723,7 +714,7 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatAssign* a)
for (size_t i = 0; i < a->vars.size; ++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;
@ -749,7 +740,7 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatFunction* f)
//
// 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.
visitLValue(f->name, defArena->freshCell());
visitLValue(f->name, defArena->freshCell(Symbol{}, f->name->location));
visitExpr(f->func);
if (auto local = f->name->as<AstExprLocal>())
@ -769,9 +760,12 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatFunction* f)
ControlFlow DataFlowGraphBuilder::visit(AstStatLocalFunction* l)
{
DefId def = defArena->freshCell();
DefId def = defArena->freshCell(l->name, l->location);
graph.localDefs[l->name] = def;
currentScope()->bindings[l->name] = def;
if (FFlag::LuauDfgScopeStackNotNull)
currentScope()->bindings[l->name] = def;
else
currentScope_DEPRECATED()->bindings[l->name] = def;
captures[l->name].allVersions.push_back(def);
visitExpr(l->func);
@ -780,7 +774,7 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatLocalFunction* l)
ControlFlow DataFlowGraphBuilder::visit(AstStatTypeAlias* t)
{
DfgScope* unreachable = makeChildScope(t->location);
DfgScope* unreachable = makeChildScope();
PushScope ps{scopeStack, unreachable};
visitGenerics(t->generics);
@ -792,7 +786,7 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatTypeAlias* t)
ControlFlow DataFlowGraphBuilder::visit(AstStatTypeFunction* f)
{
DfgScope* unreachable = makeChildScope(f->location);
DfgScope* unreachable = makeChildScope();
PushScope ps{scopeStack, unreachable};
visitExpr(f->body);
@ -802,9 +796,12 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatTypeFunction* f)
ControlFlow DataFlowGraphBuilder::visit(AstStatDeclareGlobal* d)
{
DefId def = defArena->freshCell();
DefId def = defArena->freshCell(d->name, d->nameLocation);
graph.declaredDefs[d] = def;
currentScope()->bindings[d->name] = def;
if (FFlag::LuauDfgScopeStackNotNull)
currentScope()->bindings[d->name] = def;
else
currentScope_DEPRECATED()->bindings[d->name] = def;
captures[d->name].allVersions.push_back(def);
visitType(d->type);
@ -814,31 +811,37 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatDeclareGlobal* d)
ControlFlow DataFlowGraphBuilder::visit(AstStatDeclareFunction* d)
{
DefId def = defArena->freshCell();
DefId def = defArena->freshCell(d->name, d->nameLocation);
graph.declaredDefs[d] = def;
currentScope()->bindings[d->name] = def;
if (FFlag::LuauDfgScopeStackNotNull)
currentScope()->bindings[d->name] = def;
else
currentScope_DEPRECATED()->bindings[d->name] = def;
captures[d->name].allVersions.push_back(def);
DfgScope* unreachable = makeChildScope(d->location);
DfgScope* unreachable = makeChildScope();
PushScope ps{scopeStack, unreachable};
visitGenerics(d->generics);
visitGenericPacks(d->genericPacks);
visitTypeList(d->params);
visitTypeList(d->retTypes);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
visitTypePack(d->retTypes);
else
visitTypeList(d->retTypes_DEPRECATED);
return ControlFlow::None;
}
ControlFlow DataFlowGraphBuilder::visit(AstStatDeclareClass* d)
ControlFlow DataFlowGraphBuilder::visit(AstStatDeclareExternType* d)
{
// This declaration does not "introduce" any bindings in value namespace,
// so there's no symbolic value to begin with. We'll traverse the properties
// because their type annotations may depend on something in the value namespace.
DfgScope* unreachable = makeChildScope(d->location);
DfgScope* unreachable = makeChildScope();
PushScope ps{scopeStack, unreachable};
for (AstDeclaredClassProp prop : d->props)
for (AstDeclaredExternTypeProperty prop : d->props)
visitType(prop.ty);
return ControlFlow::None;
@ -846,7 +849,7 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatDeclareClass* d)
ControlFlow DataFlowGraphBuilder::visit(AstStatError* error)
{
DfgScope* unreachable = makeChildScope(error->location);
DfgScope* unreachable = makeChildScope();
PushScope ps{scopeStack, unreachable};
for (AstStat* s : error->statements)
@ -871,19 +874,19 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExpr* e)
if (auto g = e->as<AstExprGroup>())
return visitExpr(g);
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>())
return {defArena->freshCell(), nullptr}; // ok
return {defArena->freshCell(Symbol{}, c->location), nullptr}; // ok
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>())
return {defArena->freshCell(), nullptr}; // ok
return {defArena->freshCell(Symbol{}, c->location), nullptr}; // ok
else if (auto l = e->as<AstExprLocal>())
return visitExpr(l);
else if (auto g = e->as<AstExprGlobal>())
return visitExpr(g);
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>())
return visitExpr(c);
else if (auto i = e->as<AstExprIndexName>())
@ -924,14 +927,14 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprGroup* group)
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprLocal* l)
{
DefId def = lookup(l->local);
DefId def = lookup(l->local, l->local->location);
const RefinementKey* key = keyArena->leaf(def);
return {def, key};
}
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprGlobal* g)
{
DefId def = lookup(g->name);
DefId def = lookup(g->name, g->location);
return {def, keyArena->leaf(def)};
}
@ -939,7 +942,13 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprCall* c)
{
visitExpr(c->func);
if (FFlag::LuauTypestateBuiltins2 && shouldTypestateForFirstArgument(*c) && c->args.size > 1 && isLValue(*c->args.begin()))
if (FFlag::LuauPreprocessTypestatedArgument)
{
for (AstExpr* arg : c->args)
visitExpr(arg);
}
if (shouldTypestateForFirstArgument(*c) && c->args.size > 1 && isLValue(*c->args.begin()))
{
AstExpr* firstArg = *c->args.begin();
@ -958,10 +967,7 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprCall* c)
LUAU_ASSERT(result);
Location location = currentScope()->location;
// This scope starts at the end of the call site and continues to the end of the original scope.
location.begin = c->location.end;
DfgScope* child = makeChildScope(location);
DfgScope* child = makeChildScope();
scopeStack.push_back(child);
auto [def, key] = *result;
@ -972,20 +978,31 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprCall* c)
visitLValue(firstArg, def);
}
for (AstExpr* arg : c->args)
visitExpr(arg);
if (!FFlag::LuauPreprocessTypestatedArgument)
{
for (AstExpr* arg : c->args)
visitExpr(arg);
}
// calls should be treated as subscripted.
return {defArena->freshCell(/* subscripted */ true), nullptr};
// We treat function calls as "subscripted" as they could potentially
// return a subscripted value, consider:
//
// local function foo(tbl: {[string]: woof)
// return tbl["foobarbaz"]
// end
//
// local v = foo({})
//
// We want to consider `v` to be subscripted here.
return {defArena->freshCell(Symbol{}, c->location, /*subscripted=*/true)};
}
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprIndexName* i)
{
auto [parentDef, parentKey] = visitExpr(i->expr);
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)};
}
@ -998,16 +1015,16 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprIndexExpr* i)
{
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 {defArena->freshCell(/* subscripted= */ true), nullptr};
return {defArena->freshCell(Symbol{}, i->location, /* subscripted= */ true), nullptr};
}
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprFunction* f)
{
DfgScope* signatureScope = makeChildScope(f->location, DfgScope::Function);
DfgScope* signatureScope = makeChildScope(DfgScope::Function);
PushScope ps{scopeStack, signatureScope};
if (AstLocal* self = f->self)
@ -1015,7 +1032,7 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprFunction* f)
// There's no syntax for `self` to have an annotation if using `function t:m()`
LUAU_ASSERT(!self->annotation);
DefId def = defArena->freshCell();
DefId def = defArena->freshCell(f->debugname, f->location);
graph.localDefs[self] = def;
signatureScope->bindings[self] = def;
captures[self].allVersions.push_back(def);
@ -1026,7 +1043,7 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprFunction* f)
if (param->annotation)
visitType(param->annotation);
DefId def = defArena->freshCell();
DefId def = defArena->freshCell(param, param->location);
graph.localDefs[param] = def;
signatureScope->bindings[param] = def;
captures[param].allVersions.push_back(def);
@ -1035,8 +1052,16 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprFunction* f)
if (f->varargAnnotation)
visitTypePack(f->varargAnnotation);
if (f->returnAnnotation)
visitTypeList(*f->returnAnnotation);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
if (f->returnAnnotation)
visitTypePack(f->returnAnnotation);
}
else
{
if (f->returnAnnotation_DEPRECATED)
visitTypeList(*f->returnAnnotation_DEPRECATED);
}
// TODO: function body can be re-entrant, as in mutations that occurs at the end of the function can also be
// visible to the beginning of the function, so statically speaking, the body of the function has an exit point
@ -1048,13 +1073,16 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprFunction* f)
// g() --> 5
visit(f->body);
return {defArena->freshCell(), nullptr};
return {defArena->freshCell(f->debugname, f->location), nullptr};
}
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprTable* t)
{
DefId tableCell = defArena->freshCell();
currentScope()->props[tableCell] = {};
DefId tableCell = defArena->freshCell(Symbol{}, t->location);
if (FFlag::LuauDfgScopeStackNotNull)
currentScope()->props[tableCell] = {};
else
currentScope_DEPRECATED()->props[tableCell] = {};
for (AstExprTable::Item item : t->items)
{
DataFlowResult result = visitExpr(item.value);
@ -1062,7 +1090,12 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprTable* t)
{
visitExpr(item.key);
if (auto string = item.key->as<AstExprConstantString>())
currentScope()->props[tableCell][string->value.data] = result.def;
{
if (FFlag::LuauDfgScopeStackNotNull)
currentScope()->props[tableCell][string->value.data] = result.def;
else
currentScope_DEPRECATED()->props[tableCell][string->value.data] = result.def;
}
}
}
@ -1073,7 +1106,7 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprUnary* u)
{
visitExpr(u->expr);
return {defArena->freshCell(), nullptr};
return {defArena->freshCell(Symbol{}, u->location), nullptr};
}
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprBinary* b)
@ -1081,7 +1114,7 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprBinary* b)
visitExpr(b->left);
visitExpr(b->right);
return {defArena->freshCell(), nullptr};
return {defArena->freshCell(Symbol{}, b->location), nullptr};
}
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprTypeAssertion* t)
@ -1098,7 +1131,7 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprIfElse* i)
visitExpr(i->trueExpr);
visitExpr(i->falseExpr);
return {defArena->freshCell(), nullptr};
return {defArena->freshCell(Symbol{}, i->location), nullptr};
}
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprInterpString* i)
@ -1106,18 +1139,18 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprInterpString* i)
for (AstExpr* e : i->expressions)
visitExpr(e);
return {defArena->freshCell(), nullptr};
return {defArena->freshCell(Symbol{}, i->location), nullptr};
}
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprError* error)
{
DfgScope* unreachable = makeChildScope(error->location);
DfgScope* unreachable = makeChildScope();
PushScope ps{scopeStack, unreachable};
for (AstExpr* e : error->expressions)
visitExpr(e);
return {defArena->freshCell(), nullptr};
return {defArena->freshCell(Symbol{}, error->location), nullptr};
}
void DataFlowGraphBuilder::visitLValue(AstExpr* e, DefId incomingDef)
@ -1143,12 +1176,12 @@ void DataFlowGraphBuilder::visitLValue(AstExpr* e, 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.
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;
captures[l->local].allVersions.push_back(updated);
return updated;
@ -1159,12 +1192,12 @@ DefId DataFlowGraphBuilder::visitLValue(AstExprLocal* l, 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.
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;
captures[g->name].allVersions.push_back(updated);
return updated;
@ -1177,10 +1210,10 @@ DefId DataFlowGraphBuilder::visitLValue(AstExprIndexName* i, DefId incomingDef)
{
DefId parentDef = visitExpr(i->expr).def;
DfgScope* scope = currentScope();
DfgScope* scope = FFlag::LuauDfgScopeStackNotNull ? currentScope() : currentScope_DEPRECATED();
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;
return updated;
}
@ -1193,12 +1226,12 @@ DefId DataFlowGraphBuilder::visitLValue(AstExprIndexExpr* i, DefId incomingDef)
DefId parentDef = visitExpr(i->expr).def;
visitExpr(i->index);
DfgScope* scope = currentScope();
DfgScope* scope = FFlag::LuauDfgScopeStackNotNull ? currentScope() : currentScope_DEPRECATED();
if (auto string = i->index->as<AstExprConstantString>())
{
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;
return updated;
}
@ -1206,7 +1239,7 @@ DefId DataFlowGraphBuilder::visitLValue(AstExprIndexExpr* i, DefId incomingDef)
return visitExpr(static_cast<AstExpr*>(i)).def;
}
else
return defArena->freshCell(/*subscripted=*/true);
return defArena->freshCell(Symbol{}, i->location, /*subscripted=*/true);
}
DefId DataFlowGraphBuilder::visitLValue(AstExprError* error, DefId incomingDef)
@ -1224,6 +1257,8 @@ void DataFlowGraphBuilder::visitType(AstType* t)
return visitType(f);
else if (auto tyof = t->as<AstTypeTypeof>())
return visitType(tyof);
else if (auto o = t->as<AstTypeOptional>())
return;
else if (auto u = t->as<AstTypeUnion>())
return visitType(u);
else if (auto i = t->as<AstTypeIntersection>())
@ -1234,6 +1269,8 @@ void DataFlowGraphBuilder::visitType(AstType* t)
return; // ok
else if (auto s = t->as<AstTypeSingletonString>())
return; // ok
else if (auto g = t->as<AstTypeGroup>())
return visitType(g->type);
else
handle->ice("Unknown AstType in DataFlowGraphBuilder::visitType");
}
@ -1266,7 +1303,10 @@ void DataFlowGraphBuilder::visitType(AstTypeFunction* f)
visitGenerics(f->generics);
visitGenericPacks(f->genericPacks);
visitTypeList(f->argTypes);
visitTypeList(f->returnTypes);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
visitTypePack(f->returnTypes);
else
visitTypeList(f->returnTypes_DEPRECATED);
}
void DataFlowGraphBuilder::visitType(AstTypeTypeof* t)
@ -1323,21 +1363,21 @@ void DataFlowGraphBuilder::visitTypeList(AstTypeList l)
visitTypePack(l.tailType);
}
void DataFlowGraphBuilder::visitGenerics(AstArray<AstGenericType> g)
void DataFlowGraphBuilder::visitGenerics(AstArray<AstGenericType*> g)
{
for (AstGenericType generic : g)
for (AstGenericType* generic : g)
{
if (generic.defaultValue)
visitType(generic.defaultValue);
if (generic->defaultValue)
visitType(generic->defaultValue);
}
}
void DataFlowGraphBuilder::visitGenericPacks(AstArray<AstGenericTypePack> g)
void DataFlowGraphBuilder::visitGenericPacks(AstArray<AstGenericTypePack*> g)
{
for (AstGenericTypePack generic : g)
for (AstGenericTypePack* generic : g)
{
if (generic.defaultValue)
visitTypePack(generic.defaultValue);
if (generic->defaultValue)
visitTypePack(generic->defaultValue);
}
}

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)

View file

@ -13,7 +13,6 @@
namespace Luau
{
std::string DiffPathNode::toString() const
{
switch (kind)
@ -278,7 +277,7 @@ static DifferResult diffSingleton(DifferEnvironment& env, TypeId left, TypeId ri
static DifferResult diffFunction(DifferEnvironment& env, TypeId left, TypeId right);
static DifferResult diffGeneric(DifferEnvironment& env, TypeId left, TypeId right);
static DifferResult diffNegation(DifferEnvironment& env, TypeId left, TypeId right);
static DifferResult diffClass(DifferEnvironment& env, TypeId left, TypeId right);
static DifferResult diffExternType(DifferEnvironment& env, TypeId left, TypeId right);
struct FindSeteqCounterexampleResult
{
// nullopt if no counterexample found
@ -482,14 +481,14 @@ static DifferResult diffNegation(DifferEnvironment& env, TypeId left, TypeId rig
return differResult;
}
static DifferResult diffClass(DifferEnvironment& env, TypeId left, TypeId right)
static DifferResult diffExternType(DifferEnvironment& env, TypeId left, TypeId right)
{
const ClassType* leftClass = get<ClassType>(left);
const ClassType* rightClass = get<ClassType>(right);
LUAU_ASSERT(leftClass);
LUAU_ASSERT(rightClass);
const ExternType* leftExternType = get<ExternType>(left);
const ExternType* rightExternType = get<ExternType>(right);
LUAU_ASSERT(leftExternType);
LUAU_ASSERT(rightExternType);
if (leftClass == rightClass)
if (leftExternType == rightExternType)
{
return DifferResult{};
}
@ -652,9 +651,9 @@ static DifferResult diffUsingEnv(DifferEnvironment& env, TypeId left, TypeId rig
{
return diffNegation(env, left, right);
}
else if (auto lc = get<ClassType>(left))
else if (auto lc = get<ExternType>(left))
{
return diffClass(env, left, right);
return diffExternType(env, left, right);
}
throw InternalCompilerError{"Unimplemented Simple TypeId variant for diffing"};
@ -719,7 +718,7 @@ static DifferResult diffUsingEnv(DifferEnvironment& env, TypeId left, TypeId rig
env.popVisiting();
return diffRes;
}
if (auto le = get<Luau::Unifiable::Error>(left))
if (auto le = get<ErrorType>(left))
{
// TODO: return debug-friendly result state
env.popVisiting();
@ -945,14 +944,12 @@ std::vector<std::pair<TypeId, TypeId>>::const_reverse_iterator DifferEnvironment
return visitingStack.crend();
}
DifferResult diff(TypeId ty1, TypeId ty2)
{
DifferEnvironment differEnv{ty1, ty2, std::nullopt, std::nullopt};
return diffUsingEnv(differEnv, ty1, ty2);
}
DifferResult diffWithSymbols(TypeId ty1, TypeId ty2, std::optional<std::string> symbol1, std::optional<std::string> symbol2)
{
DifferEnvironment differEnv{ty1, ty2, symbol1, symbol2};
@ -963,7 +960,7 @@ bool isSimple(TypeId ty)
{
ty = follow(ty);
// TODO: think about GenericType, etc.
return get<PrimitiveType>(ty) || get<SingletonType>(ty) || get<AnyType>(ty) || get<NegationType>(ty) || get<ClassType>(ty) ||
return get<PrimitiveType>(ty) || get<SingletonType>(ty) || get<AnyType>(ty) || get<NegationType>(ty) || get<ExternType>(ty) ||
get<UnknownType>(ty) || get<NeverType>(ty);
}

View file

@ -1,107 +1,13 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/BuiltinDefinitions.h"
LUAU_FASTFLAG(LuauMathMap)
LUAU_FASTFLAGVARIABLE(LuauVectorDefinitions)
LUAU_FASTFLAG(LuauDeclareExternType)
LUAU_FASTFLAG(LuauTypeFunOptional)
namespace Luau
{
// TODO: there has to be a better way, like splitting up per library
static const std::string kBuiltinDefinitionLuaSrcChecked_DEPRECATED = R"BUILTIN_SRC(
declare bit32: {
band: @checked (...number) -> number,
bor: @checked (...number) -> number,
bxor: @checked (...number) -> number,
btest: @checked (number, ...number) -> boolean,
rrotate: @checked (x: number, disp: number) -> number,
lrotate: @checked (x: number, disp: number) -> number,
lshift: @checked (x: number, disp: number) -> number,
arshift: @checked (x: number, disp: number) -> number,
rshift: @checked (x: number, disp: number) -> number,
bnot: @checked (x: number) -> number,
extract: @checked (n: number, field: number, width: number?) -> number,
replace: @checked (n: number, v: number, field: number, width: number?) -> number,
countlz: @checked (n: number) -> number,
countrz: @checked (n: number) -> number,
byteswap: @checked (n: number) -> number,
}
declare math: {
frexp: @checked (n: number) -> (number, number),
ldexp: @checked (s: number, e: number) -> number,
fmod: @checked (x: number, y: number) -> number,
modf: @checked (n: number) -> (number, number),
pow: @checked (x: number, y: number) -> number,
exp: @checked (n: number) -> number,
ceil: @checked (n: number) -> number,
floor: @checked (n: number) -> number,
abs: @checked (n: number) -> number,
sqrt: @checked (n: number) -> number,
log: @checked (n: number, base: number?) -> number,
log10: @checked (n: number) -> number,
rad: @checked (n: number) -> number,
deg: @checked (n: number) -> number,
sin: @checked (n: number) -> number,
cos: @checked (n: number) -> number,
tan: @checked (n: number) -> number,
sinh: @checked (n: number) -> number,
cosh: @checked (n: number) -> number,
tanh: @checked (n: number) -> number,
atan: @checked (n: number) -> number,
acos: @checked (n: number) -> number,
asin: @checked (n: number) -> number,
atan2: @checked (y: number, x: number) -> number,
min: @checked (number, ...number) -> number,
max: @checked (number, ...number) -> number,
pi: number,
huge: number,
randomseed: @checked (seed: number) -> (),
random: @checked (number?, number?) -> number,
sign: @checked (n: number) -> number,
clamp: @checked (n: number, min: number, max: number) -> number,
noise: @checked (x: number, y: number?, z: number?) -> number,
round: @checked (n: number) -> number,
}
type DateTypeArg = {
year: number,
month: number,
day: number,
hour: number?,
min: number?,
sec: number?,
isdst: boolean?,
}
type DateTypeResult = {
year: number,
month: number,
wday: number,
yday: number,
day: number,
hour: number,
min: number,
sec: number,
isdst: boolean,
}
declare os: {
time: (time: DateTypeArg?) -> number,
date: ((formatString: "*t" | "!*t", time: number?) -> DateTypeResult) & ((formatString: string?, time: number?) -> string),
difftime: (t2: DateTypeResult | number, t1: DateTypeResult | number) -> number,
clock: () -> number,
}
static const std::string kBuiltinDefinitionBaseSrc = R"BUILTIN_SRC(
@checked declare function require(target: any): any
@ -149,88 +55,12 @@ declare function loadstring<A...>(src: string, chunkname: string?): (((A...) ->
@checked declare function newproxy(mt: boolean?): any
declare coroutine: {
create: <A..., R...>(f: (A...) -> R...) -> thread,
resume: <A..., R...>(co: thread, A...) -> (boolean, R...),
running: () -> thread,
status: @checked (co: thread) -> "dead" | "running" | "normal" | "suspended",
wrap: <A..., R...>(f: (A...) -> R...) -> ((A...) -> R...),
yield: <A..., R...>(A...) -> R...,
isyieldable: () -> boolean,
close: @checked (co: thread) -> (boolean, any)
}
declare table: {
concat: <V>(t: {V}, sep: string?, i: number?, j: number?) -> string,
insert: (<V>(t: {V}, value: V) -> ()) & (<V>(t: {V}, pos: number, value: V) -> ()),
maxn: <V>(t: {V}) -> number,
remove: <V>(t: {V}, number?) -> V?,
sort: <V>(t: {V}, comp: ((V, V) -> boolean)?) -> (),
create: <V>(count: number, value: V?) -> {V},
find: <V>(haystack: {V}, needle: V, init: number?) -> number?,
unpack: <V>(list: {V}, i: number?, j: number?) -> ...V,
pack: <V>(...V) -> { n: number, [number]: V },
getn: <V>(t: {V}) -> number,
foreach: <K, V>(t: {[K]: V}, f: (K, V) -> ()) -> (),
foreachi: <V>({V}, (number, V) -> ()) -> (),
move: <V>(src: {V}, a: number, b: number, t: number, dst: {V}?) -> {V},
clear: <K, V>(table: {[K]: V}) -> (),
isfrozen: <K, V>(t: {[K]: V}) -> boolean,
}
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),
}
declare utf8: {
char: @checked (...number) -> string,
charpattern: string,
codes: @checked (str: string) -> ((string, number) -> (number, number), string, number),
codepoint: @checked (str: string, i: number?, j: number?) -> ...number,
len: @checked (s: string, i: number?, j: number?) -> (number?, number?),
offset: @checked (s: string, n: number?, i: number?) -> number,
}
-- Cannot use `typeof` here because it will produce a polytype when we expect a monotype.
declare function unpack<V>(tab: {V}, i: number?, j: number?): ...V
--- Buffer API
declare buffer: {
create: @checked (size: number) -> buffer,
fromstring: @checked (str: string) -> buffer,
tostring: @checked (b: buffer) -> string,
len: @checked (b: buffer) -> number,
copy: @checked (target: buffer, targetOffset: number, source: buffer, sourceOffset: number?, count: number?) -> (),
fill: @checked (b: buffer, offset: number, value: number, count: number?) -> (),
readi8: @checked (b: buffer, offset: number) -> number,
readu8: @checked (b: buffer, offset: number) -> number,
readi16: @checked (b: buffer, offset: number) -> number,
readu16: @checked (b: buffer, offset: number) -> number,
readi32: @checked (b: buffer, offset: number) -> number,
readu32: @checked (b: buffer, offset: number) -> number,
readf32: @checked (b: buffer, offset: number) -> number,
readf64: @checked (b: buffer, offset: number) -> number,
writei8: @checked (b: buffer, offset: number, value: number) -> (),
writeu8: @checked (b: buffer, offset: number, value: number) -> (),
writei16: @checked (b: buffer, offset: number, value: number) -> (),
writeu16: @checked (b: buffer, offset: number, value: number) -> (),
writei32: @checked (b: buffer, offset: number, value: number) -> (),
writeu32: @checked (b: buffer, offset: number, value: number) -> (),
writef32: @checked (b: buffer, offset: number, value: number) -> (),
writef64: @checked (b: buffer, offset: number, value: number) -> (),
readstring: @checked (b: buffer, offset: number, count: number) -> string,
writestring: @checked (b: buffer, offset: number, value: string, count: number?) -> (),
}
)BUILTIN_SRC";
static const std::string kBuiltinDefinitionLuaSrcChecked = R"BUILTIN_SRC(
static const std::string kBuiltinDefinitionBit32Src = R"BUILTIN_SRC(
declare bit32: {
band: @checked (...number) -> number,
@ -250,6 +80,10 @@ declare bit32: {
byteswap: @checked (n: number) -> number,
}
)BUILTIN_SRC";
static const std::string kBuiltinDefinitionMathSrc = R"BUILTIN_SRC(
declare math: {
frexp: @checked (n: number) -> (number, number),
ldexp: @checked (s: number, e: number) -> number,
@ -294,8 +128,13 @@ declare math: {
noise: @checked (x: number, y: number?, z: number?) -> number,
round: @checked (n: number) -> number,
map: @checked (x: number, inmin: number, inmax: number, outmin: number, outmax: number) -> number,
lerp: @checked (a: number, b: number, t: number) -> number,
}
)BUILTIN_SRC";
static const std::string kBuiltinDefinitionOsSrc = R"BUILTIN_SRC(
type DateTypeArg = {
year: number,
month: number,
@ -325,51 +164,9 @@ declare os: {
clock: () -> number,
}
@checked declare function require(target: any): any
)BUILTIN_SRC";
@checked declare function getfenv(target: any): { [string]: any }
declare _G: any
declare _VERSION: string
declare function gcinfo(): number
declare function print<T...>(...: T...)
declare function type<T>(value: T): string
declare function typeof<T>(value: T): string
-- `assert` has a magic function attached that will give more detailed type information
declare function assert<T>(value: T, errorMessage: string?): T
declare function error<T>(message: T, level: number?): never
declare function tostring<T>(value: T): string
declare function tonumber<T>(value: T, radix: number?): number?
declare function rawequal<T1, T2>(a: T1, b: T2): boolean
declare function rawget<K, V>(tab: {[K]: V}, k: K): V
declare function rawset<K, V>(tab: {[K]: V}, k: K, v: V): {[K]: V}
declare function rawlen<K, V>(obj: {[K]: V} | string): number
declare function setfenv<T..., R...>(target: number | (T...) -> R..., env: {[string]: any}): ((T...) -> R...)?
declare function ipairs<V>(tab: {V}): (({V}, number) -> (number?, V), {V}, number)
declare function pcall<A..., R...>(f: (A...) -> R..., ...: A...): (boolean, R...)
-- FIXME: The actual type of `xpcall` is:
-- <E, A..., R1..., R2...>(f: (A...) -> R1..., err: (E) -> R2..., A...) -> (true, R1...) | (false, R2...)
-- Since we can't represent the return value, we use (boolean, R1...).
declare function xpcall<E, A..., R1..., R2...>(f: (A...) -> R1..., err: (E) -> R2..., ...: A...): (boolean, R1...)
-- `select` has a magic function attached to provide more detailed type information
declare function select<A...>(i: string | number, ...: A...): ...any
-- FIXME: This type is not entirely correct - `loadstring` returns a function or
-- (nil, string).
declare function loadstring<A...>(src: string, chunkname: string?): (((A...) -> any)?, string?)
@checked declare function newproxy(mt: boolean?): any
static const std::string kBuiltinDefinitionCoroutineSrc = R"BUILTIN_SRC(
declare coroutine: {
create: <A..., R...>(f: (A...) -> R...) -> thread,
@ -382,6 +179,10 @@ declare coroutine: {
close: @checked (co: thread) -> (boolean, any)
}
)BUILTIN_SRC";
static const std::string kBuiltinDefinitionTableSrc = R"BUILTIN_SRC(
declare table: {
concat: <V>(t: {V}, sep: string?, i: number?, j: number?) -> string,
insert: (<V>(t: {V}, value: V) -> ()) & (<V>(t: {V}, pos: number, value: V) -> ()),
@ -404,11 +205,19 @@ declare table: {
isfrozen: <K, V>(t: {[K]: V}) -> boolean,
}
)BUILTIN_SRC";
static const std::string kBuiltinDefinitionDebugSrc = 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...),
info: ((thread: thread, level: number, options: string) -> ...any) & ((level: number, options: string) -> ...any) & (<A..., R1...>(func: (A...) -> R1..., options: string) -> ...any),
traceback: ((message: string?, level: number?) -> string) & ((thread: thread, message: string?, level: number?) -> string),
}
)BUILTIN_SRC";
static const std::string kBuiltinDefinitionUtf8Src = R"BUILTIN_SRC(
declare utf8: {
char: @checked (...number) -> string,
charpattern: string,
@ -418,10 +227,9 @@ declare utf8: {
offset: @checked (s: string, n: number?, i: number?) -> number,
}
-- Cannot use `typeof` here because it will produce a polytype when we expect a monotype.
declare function unpack<V>(tab: {V}, i: number?, j: number?): ...V
)BUILTIN_SRC";
static const std::string kBuiltinDefinitionBufferSrc = R"BUILTIN_SRC(
--- Buffer API
declare buffer: {
create: @checked (size: number) -> buffer,
@ -448,17 +256,53 @@ declare buffer: {
writef64: @checked (b: buffer, offset: number, value: number) -> (),
readstring: @checked (b: buffer, offset: number, count: number) -> string,
writestring: @checked (b: buffer, offset: number, value: string, count: number?) -> (),
readbits: @checked (b: buffer, bitOffset: number, bitCount: number) -> number,
writebits: @checked (b: buffer, bitOffset: number, bitCount: number, value: number) -> (),
}
)BUILTIN_SRC";
static const std::string kBuiltinDefinitionVectorSrc = R"BUILTIN_SRC(
static const std::string kBuiltinDefinitionVectorSrc = (FFlag::LuauDeclareExternType)
? R"BUILTIN_SRC(
-- TODO: this will be replaced with a built-in primitive type
declare class vector end
-- 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,
create: @checked (x: number, y: number, z: number?) -> vector,
magnitude: @checked (vec: vector) -> number,
normalize: @checked (vec: vector) -> vector,
cross: @checked (vec1: vector, vec2: vector) -> vector,
dot: @checked (vec1: vector, vec2: vector) -> number,
angle: @checked (vec1: vector, vec2: vector, axis: vector?) -> number,
floor: @checked (vec: vector) -> vector,
ceil: @checked (vec: vector) -> vector,
abs: @checked (vec: vector) -> vector,
sign: @checked (vec: vector) -> vector,
clamp: @checked (vec: vector, min: vector, max: vector) -> vector,
max: @checked (vector, ...vector) -> vector,
min: @checked (vector, ...vector) -> vector,
zero: vector,
one: vector,
}
)BUILTIN_SRC"
: R"BUILTIN_SRC(
-- While vector would have been better represented as a built-in primitive type, type solver class handling covers most of the properties
declare class vector
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,
@ -480,10 +324,132 @@ declare vector: {
std::string getBuiltinDefinitionSource()
{
std::string result = FFlag::LuauMathMap ? kBuiltinDefinitionLuaSrcChecked : kBuiltinDefinitionLuaSrcChecked_DEPRECATED;
std::string result = kBuiltinDefinitionBaseSrc;
if (FFlag::LuauVectorDefinitions)
result += kBuiltinDefinitionVectorSrc;
result += kBuiltinDefinitionBit32Src;
result += kBuiltinDefinitionMathSrc;
result += kBuiltinDefinitionOsSrc;
result += kBuiltinDefinitionCoroutineSrc;
result += kBuiltinDefinitionTableSrc;
result += kBuiltinDefinitionDebugSrc;
result += kBuiltinDefinitionUtf8Src;
result += kBuiltinDefinitionBufferSrc;
result += kBuiltinDefinitionVectorSrc;
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;
}

File diff suppressed because it is too large Load diff

View file

@ -8,6 +8,7 @@
#include "Luau/StringUtils.h"
#include "Luau/ToString.h"
#include "Luau/Type.h"
#include "Luau/TypeChecker2.h"
#include "Luau/TypeFunction.h"
#include <optional>
@ -17,6 +18,7 @@
#include <unordered_set>
LUAU_FASTINTVARIABLE(LuauIndentTypeMismatchMaxTypeLength, 10)
LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
static std::string wrongNumberOfArgsString(
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
static const std::unordered_map<std::string, const char*> kBinaryOps{
static const std::unordered_map<std::string, const char*> DEPRECATED_kBinaryOps{
{"add", "+"},
{"sub", "-"},
{"mul", "*"},
@ -84,12 +86,27 @@ static const std::unordered_map<std::string, const char*> kBinaryOps{
{"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
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
// 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
{
@ -116,7 +133,10 @@ struct ErrorConverter
size_t luauIndentTypeMismatchMaxTypeLength = size_t(FInt::LuauIndentTypeMismatchMaxTypeLength);
if (givenType.length() <= luauIndentTypeMismatchMaxTypeLength || wantedType.length() <= luauIndentTypeMismatchMaxTypeLength)
return "Type " + given + " could not be converted into " + wanted;
return "Type\n " + given + "\ncould not be converted into\n " + wanted;
if (FFlag::LuauImproveTypePathsInErrors)
return "Type\n\t" + given + "\ncould not be converted into\n\t" + wanted;
else
return "Type\n " + given + "\ncould not be converted into\n " + wanted;
};
if (givenTypeName == wantedTypeName)
@ -183,7 +203,7 @@ struct ErrorConverter
TypeId t = follow(e.table);
if (get<TableType>(t))
return "Key '" + e.key + "' not found in table '" + Luau::toString(t) + "'";
else if (get<ClassType>(t))
else if (get<ExternType>(t))
return "Key '" + e.key + "' not found in class '" + Luau::toString(t) + "'";
else
return "Type '" + Luau::toString(e.table) + "' does not have key '" + e.key + "'";
@ -351,7 +371,7 @@ struct ErrorConverter
std::string s = "Key '" + e.key + "' not found in ";
TypeId t = follow(e.table);
if (get<ClassType>(t))
if (get<ExternType>(t))
s += "class";
else
s += "table";
@ -382,8 +402,8 @@ struct ErrorConverter
std::optional<TypeId> metatable;
if (const MetatableType* mtType = get<MetatableType>(type))
metatable = mtType->metatable;
else if (const ClassType* classType = get<ClassType>(type))
metatable = classType->metatable;
else if (const ExternType* externType = get<ExternType>(type))
metatable = externType->metatable;
if (!metatable)
return std::nullopt;
@ -591,7 +611,7 @@ struct ErrorConverter
return ss;
}
std::string operator()(const DynamicPropertyLookupOnClassesUnsafe& e) const
std::string operator()(const DynamicPropertyLookupOnExternTypesUnsafe& e) const
{
return "Attempting a dynamic property access on type '" + Luau::toString(e.ty) + "' is unsafe and may cause exceptions at runtime";
}
@ -601,7 +621,7 @@ struct ErrorConverter
auto tfit = get<TypeFunctionInstanceType>(e.ty);
LUAU_ASSERT(tfit); // Luau analysis has actually done something wrong if this type is not a type function.
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
if (auto unaryString = kUnaryOps.find(tfit->function->name); unaryString != kUnaryOps.end())
@ -638,7 +658,8 @@ struct ErrorConverter
}
// 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 ";
@ -692,10 +713,10 @@ 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" +
"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";
}
// Everything should be specialized above to report a more descriptive error that hopefully does not mention "type functions" explicitly.
@ -751,8 +772,15 @@ struct ErrorConverter
std::string operator()(const NonStrictFunctionDefinitionError& e) const
{
return "Argument " + e.argument + " with type '" + toString(e.argumentType) + "' in function '" + e.functionName +
"' is used in a way that will run time error";
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 +
"' is used in a way that will run time error";
}
}
std::string operator()(const PropertyAccessViolation& e) const
@ -791,6 +819,11 @@ struct ErrorConverter
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 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;
}
bool DynamicPropertyLookupOnClassesUnsafe::operator==(const DynamicPropertyLookupOnClassesUnsafe& rhs) const
bool DynamicPropertyLookupOnExternTypesUnsafe::operator==(const DynamicPropertyLookupOnExternTypesUnsafe& rhs) const
{
return ty == rhs.ty;
}
@ -1178,6 +1211,11 @@ bool UserDefinedTypeFunctionError::operator==(const UserDefinedTypeFunctionError
return message == rhs.message;
}
bool ReservedIdentifier::operator==(const ReservedIdentifier& rhs) const
{
return name == rhs.name;
}
bool CannotAssignToNever::operator==(const CannotAssignToNever& rhs) const
{
if (cause.size() != rhs.cause.size())
@ -1353,7 +1391,7 @@ void copyError(T& e, TypeArena& destArena, CloneState& cloneState)
e.wantedTp = clone(e.wantedTp);
e.givenTp = clone(e.givenTp);
}
else if constexpr (std::is_same_v<T, DynamicPropertyLookupOnClassesUnsafe>)
else if constexpr (std::is_same_v<T, DynamicPropertyLookupOnExternTypesUnsafe>)
e.ty = clone(e.ty);
else if constexpr (std::is_same_v<T, UninhabitedTypeFunction>)
e.ty = clone(e.ty);
@ -1397,6 +1435,9 @@ void copyError(T& e, TypeArena& destArena, CloneState& cloneState)
for (auto& ty : e.cause)
ty = clone(ty);
}
else if constexpr (std::is_same_v<T, ReservedIdentifier>)
{
}
else
static_assert(always_false_v<T>, "Non-exhaustive type switch");
}

View file

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

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,6 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/Frontend.h"
#include "Luau/AnyTypeSummary.h"
#include "Luau/BuiltinDefinitions.h"
#include "Luau/Clone.h"
#include "Luau/Common.h"
@ -10,8 +9,10 @@
#include "Luau/ConstraintSolver.h"
#include "Luau/DataFlowGraph.h"
#include "Luau/DcrLogger.h"
#include "Luau/EqSatSimplification.h"
#include "Luau/FileResolver.h"
#include "Luau/NonStrictTypeChecker.h"
#include "Luau/NotNull.h"
#include "Luau/Parser.h"
#include "Luau/Scope.h"
#include "Luau/StringUtils.h"
@ -37,19 +38,16 @@ LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTINT(LuauTarjanChildLimit)
LUAU_FASTFLAG(LuauInferInNoCheckMode)
LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3)
LUAU_FASTFLAGVARIABLE(LuauStoreCommentsForDefinitionFiles)
LUAU_FASTFLAG(LuauSolverV2)
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauRethrowKnownExceptions, false)
LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson)
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJsonFile)
LUAU_FASTFLAGVARIABLE(DebugLuauForbidInternalTypes)
LUAU_FASTFLAGVARIABLE(DebugLuauForceStrictMode)
LUAU_FASTFLAGVARIABLE(DebugLuauForceNonStrictMode)
LUAU_FASTFLAGVARIABLE(LuauUserDefinedTypeFunctionNoEvaluation)
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauRunCustomModuleChecks, false)
LUAU_FASTFLAGVARIABLE(LuauMoreThoroughCycleDetection)
LUAU_FASTFLAG(StudioReportLuauAny2)
LUAU_FASTFLAGVARIABLE(LuauStoreDFGOnModule2)
LUAU_FASTFLAG(LuauTypeFunResultInAutocomplete)
namespace Luau
{
@ -79,6 +77,20 @@ struct BuildQueueItem
Frontend::Stats stats;
};
struct BuildQueueWorkState
{
std::function<void(std::function<void()> task)> executeTask;
std::vector<BuildQueueItem> buildQueueItems;
std::mutex mtx;
std::condition_variable cv;
std::vector<size_t> readyQueueItems;
size_t processing = 0;
size_t remaining = 0;
};
std::optional<Mode> parseMode(const std::vector<HotComment>& hotcomments)
{
for (const HotComment& hc : hotcomments)
@ -117,9 +129,9 @@ static void generateDocumentationSymbols(TypeId ty, const std::string& rootName)
prop.documentationSymbol = rootName + "." + name;
}
}
else if (ClassType* ctv = getMutable<ClassType>(ty))
else if (ExternType* etv = getMutable<ExternType>(ty))
{
for (auto& [name, prop] : ctv->props)
for (auto& [name, prop] : etv->props)
{
prop.documentationSymbol = rootName + "." + name;
}
@ -136,7 +148,7 @@ static ParseResult parseSourceForModule(std::string_view source, Luau::SourceMod
sourceModule.root = parseResult.root;
sourceModule.mode = Mode::Definition;
if (FFlag::LuauStoreCommentsForDefinitionFiles && options.captureComments)
if (options.captureComments)
{
sourceModule.hotcomments = parseResult.hotcomments;
sourceModule.commentLocations = parseResult.commentLocations;
@ -287,8 +299,7 @@ static void filterLintOptions(LintOptions& lintOptions, const std::vector<HotCom
std::vector<RequireCycle> getRequireCycles(
const FileResolver* resolver,
const std::unordered_map<ModuleName, std::shared_ptr<SourceNode>>& sourceNodes,
const SourceNode* start,
bool stopAtFirst = false
const SourceNode* start
)
{
std::vector<RequireCycle> result;
@ -358,9 +369,6 @@ std::vector<RequireCycle> getRequireCycles(
{
result.push_back({depLocation, std::move(cycle)});
if (stopAtFirst)
return result;
// note: if we didn't find a cycle, all nodes that we've seen don't depend [transitively] on start
// so it's safe to *only* clear seen vector when we find a cycle
// if we don't do it, we will not have correct reporting for some cycles
@ -447,20 +455,6 @@ CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOption
if (item.name == name)
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;
@ -491,7 +485,8 @@ std::vector<ModuleName> Frontend::checkQueuedModules(
std::swap(currModuleQueue, moduleQueue);
DenseHashSet<Luau::ModuleName> seen{{}};
std::vector<BuildQueueItem> buildQueueItems;
std::shared_ptr<BuildQueueWorkState> state = std::make_shared<BuildQueueWorkState>();
for (const ModuleName& name : currModuleQueue)
{
@ -515,18 +510,18 @@ std::vector<ModuleName> Frontend::checkQueuedModules(
}
);
addBuildQueueItems(buildQueueItems, queue, cycleDetected, seen, frontendOptions);
addBuildQueueItems(state->buildQueueItems, queue, cycleDetected, seen, frontendOptions);
}
if (buildQueueItems.empty())
if (state->buildQueueItems.empty())
return {};
// 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++)
for (size_t i = 0; i < state->buildQueueItems.size(); i++)
{
BuildQueueItem& item = buildQueueItems[i];
BuildQueueItem& item = state->buildQueueItems[i];
moduleNameToQueue[item.name] = i;
}
@ -539,67 +534,13 @@ std::vector<ModuleName> Frontend::checkQueuedModules(
};
}
std::mutex mtx;
std::condition_variable cv;
std::vector<size_t> readyQueueItems;
state->executeTask = executeTask;
state->remaining = state->buildQueueItems.size();
size_t processing = 0;
size_t remaining = buildQueueItems.size();
auto itemTask = [&](size_t i)
// Record dependencies between modules
for (size_t i = 0; i < state->buildQueueItems.size(); 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];
BuildQueueItem& item = state->buildQueueItems[i];
for (const ModuleName& dep : item.sourceNode->requireSet)
{
@ -609,41 +550,45 @@ std::vector<ModuleName> Frontend::checkQueuedModules(
{
item.dirtyDependencies++;
buildQueueItems[moduleNameToQueue[dep]].reverseDeps.push_back(i);
state->buildQueueItems[moduleNameToQueue[dep]].reverseDeps.push_back(i);
}
}
}
if (item.dirtyDependencies == 0)
sendItemTask(i);
}
// Not a single item was found, a cycle in the graph was hit
if (processing == 0)
sendCycleItemTask();
// In the first pass, check all modules with no pending dependencies
for (size_t i = 0; i < state->buildQueueItems.size(); i++)
{
if (state->buildQueueItems[i].dirtyDependencies == 0)
sendQueueItemTask(state, i);
}
// If not a single item was found, a cycle in the graph was hit
if (state->processing == 0)
sendQueueCycleItemTask(state);
std::vector<size_t> nextItems;
std::optional<size_t> itemWithException;
bool cancelled = false;
while (remaining != 0)
while (state->remaining != 0)
{
{
std::unique_lock guard(mtx);
std::unique_lock guard(state->mtx);
// If nothing is ready yet, wait
cv.wait(
state->cv.wait(
guard,
[&readyQueueItems]
[state]
{
return !readyQueueItems.empty();
return !state->readyQueueItems.empty();
}
);
// Handle checked items
for (size_t i : readyQueueItems)
for (size_t i : state->readyQueueItems)
{
const BuildQueueItem& item = buildQueueItems[i];
const BuildQueueItem& item = state->buildQueueItems[i];
// If exception was thrown, stop adding new items and wait for processing items to complete
if (item.exception)
@ -660,7 +605,7 @@ std::vector<ModuleName> Frontend::checkQueuedModules(
// Notify items that were waiting for this dependency
for (size_t reverseDep : item.reverseDeps)
{
BuildQueueItem& reverseDepItem = buildQueueItems[reverseDep];
BuildQueueItem& reverseDepItem = state->buildQueueItems[reverseDep];
LUAU_ASSERT(reverseDepItem.dirtyDependencies != 0);
reverseDepItem.dirtyDependencies--;
@ -671,26 +616,26 @@ std::vector<ModuleName> Frontend::checkQueuedModules(
}
}
LUAU_ASSERT(processing >= readyQueueItems.size());
processing -= readyQueueItems.size();
LUAU_ASSERT(state->processing >= state->readyQueueItems.size());
state->processing -= state->readyQueueItems.size();
LUAU_ASSERT(remaining >= readyQueueItems.size());
remaining -= readyQueueItems.size();
readyQueueItems.clear();
LUAU_ASSERT(state->remaining >= state->readyQueueItems.size());
state->remaining -= state->readyQueueItems.size();
state->readyQueueItems.clear();
}
if (progress)
{
if (!progress(buildQueueItems.size() - remaining, buildQueueItems.size()))
if (!progress(state->buildQueueItems.size() - state->remaining, state->buildQueueItems.size()))
cancelled = true;
}
// Items cannot be submitted while holding the lock
for (size_t i : nextItems)
sendItemTask(i);
sendQueueItemTask(state, i);
nextItems.clear();
if (processing == 0)
if (state->processing == 0)
{
// Typechecking might have been cancelled by user, don't return partial results
if (cancelled)
@ -698,19 +643,19 @@ std::vector<ModuleName> Frontend::checkQueuedModules(
// We might have stopped because of a pending exception
if (itemWithException)
recordItemResult(buildQueueItems[*itemWithException]);
recordItemResult(state->buildQueueItems[*itemWithException]);
}
// If we aren't done, but don't have anything processing, we hit a cycle
if (remaining != 0 && processing == 0)
sendCycleItemTask();
if (state->remaining != 0 && state->processing == 0)
sendQueueCycleItemTask(state);
}
std::vector<ModuleName> checkedModules;
checkedModules.reserve(buildQueueItems.size());
checkedModules.reserve(state->buildQueueItems.size());
for (size_t i = 0; i < buildQueueItems.size(); i++)
checkedModules.push_back(std::move(buildQueueItems[i].name));
for (size_t i = 0; i < state->buildQueueItems.size(); i++)
checkedModules.push_back(std::move(state->buildQueueItems[i].name));
return checkedModules;
}
@ -748,6 +693,32 @@ std::optional<CheckResult> Frontend::getCheckResult(const ModuleName& name, bool
return checkResult;
}
std::vector<ModuleName> Frontend::getRequiredScripts(const ModuleName& name)
{
RequireTraceResult require = requireTrace[name];
if (isDirty(name))
{
std::optional<SourceCode> source = fileResolver->readSource(name);
if (!source)
{
return {};
}
const Config& config = configResolver->getConfig(name);
ParseOptions opts = config.parseOptions;
opts.captureComments = true;
SourceModule result = parse(name, source->source, opts);
result.type = source->type;
require = traceRequires(fileResolver, result.root, name);
}
std::vector<std::string> requiredModuleNames;
requiredModuleNames.reserve(require.requireList.size());
for (const auto& [moduleName, _] : require.requireList)
{
requiredModuleNames.push_back(moduleName);
}
return requiredModuleNames;
}
bool Frontend::parseGraph(
std::vector<ModuleName>& buildQueue,
const ModuleName& root,
@ -796,6 +767,13 @@ bool Frontend::parseGraph(
topseen = Permanent;
buildQueue.push_back(top->name);
// at this point we know all valid dependencies are processed into SourceNodes
for (const ModuleName& dep : top->requireSet)
{
if (auto it = sourceNodes.find(dep); it != sourceNodes.end())
it->second->dependents.insert(top->name);
}
}
else
{
@ -884,18 +862,11 @@ void Frontend::addBuildQueueItems(
data.environmentScope = getModuleEnvironment(*sourceModule, data.config, frontendOptions.forAutocomplete);
data.recordJsonLog = FFlag::DebugLuauLogSolverToJson;
const Mode mode = sourceModule->mode.value_or(data.config.mode);
// in the future we could replace toposort with an algorithm that can flag cyclic nodes by itself
// however, for now getRequireCycles isn't expensive in practice on the cases we care about, and long term
// all correct programs must be acyclic so this code triggers rarely
if (cycleDetected)
{
if (FFlag::LuauMoreThoroughCycleDetection)
data.requireCycles = getRequireCycles(fileResolver, sourceNodes, sourceNode.get(), false);
else
data.requireCycles = getRequireCycles(fileResolver, sourceNodes, sourceNode.get(), mode == Mode::NoCheck);
}
data.requireCycles = getRequireCycles(fileResolver, sourceNodes, sourceNode.get());
data.options = frontendOptions;
@ -980,7 +951,7 @@ void Frontend::checkBuildQueueItem(BuildQueueItem& item)
item.stats.timeCheck += duration;
item.stats.filesStrict += 1;
if (DFFlag::LuauRunCustomModuleChecks && item.options.customModuleCheck)
if (item.options.customModuleCheck)
item.options.customModuleCheck(sourceModule, *moduleForAutocomplete);
item.module = moduleForAutocomplete;
@ -1000,7 +971,7 @@ void Frontend::checkBuildQueueItem(BuildQueueItem& item)
item.stats.filesStrict += mode == Mode::Strict;
item.stats.filesNonstrict += mode == Mode::Nonstrict;
if (DFFlag::LuauRunCustomModuleChecks && item.options.customModuleCheck)
if (item.options.customModuleCheck)
item.options.customModuleCheck(sourceModule, *module);
if (FFlag::LuauSolverV2 && mode == Mode::NoCheck)
@ -1032,6 +1003,8 @@ void Frontend::checkBuildQueueItem(BuildQueueItem& item)
freeze(module->interfaceTypes);
module->internalTypes.clear();
module->defArena.allocator.clear();
module->keyArena.allocator.clear();
module->astTypes.clear();
module->astTypePacks.clear();
@ -1085,17 +1058,35 @@ void Frontend::recordItemResult(const BuildQueueItem& item)
if (item.exception)
std::rethrow_exception(item.exception);
bool replacedModule = false;
if (item.options.forAutocomplete)
{
moduleResolverForAutocomplete.setModule(item.name, item.module);
replacedModule = moduleResolverForAutocomplete.setModule(item.name, item.module);
item.sourceNode->dirtyModuleForAutocomplete = false;
}
else
{
moduleResolver.setModule(item.name, item.module);
replacedModule = moduleResolver.setModule(item.name, item.module);
item.sourceNode->dirtyModule = false;
}
if (replacedModule)
{
LUAU_TIMETRACE_SCOPE("Frontend::invalidateDependentModules", "Frontend");
LUAU_TIMETRACE_ARGUMENT("name", item.name.c_str());
traverseDependents(
item.name,
[forAutocomplete = item.options.forAutocomplete](SourceNode& sourceNode)
{
bool traverseSubtree = !sourceNode.hasInvalidModuleDependency(forAutocomplete);
sourceNode.setInvalidModuleDependency(true, forAutocomplete);
return traverseSubtree;
}
);
}
item.sourceNode->setInvalidModuleDependency(false, item.options.forAutocomplete);
stats.timeCheck += item.stats.timeCheck;
stats.timeLint += item.stats.timeLint;
@ -1103,6 +1094,72 @@ void Frontend::recordItemResult(const BuildQueueItem& item)
stats.filesNonstrict += item.stats.filesNonstrict;
}
void Frontend::performQueueItemTask(std::shared_ptr<BuildQueueWorkState> state, size_t itemPos)
{
BuildQueueItem& item = state->buildQueueItems[itemPos];
if (DFFlag::LuauRethrowKnownExceptions)
{
try
{
checkBuildQueueItem(item);
}
catch (const Luau::InternalCompilerError&)
{
item.exception = std::current_exception();
}
}
else
{
try
{
checkBuildQueueItem(item);
}
catch (...)
{
item.exception = std::current_exception();
}
}
{
std::unique_lock guard(state->mtx);
state->readyQueueItems.push_back(itemPos);
}
state->cv.notify_one();
}
void Frontend::sendQueueItemTask(std::shared_ptr<BuildQueueWorkState> state, size_t itemPos)
{
BuildQueueItem& item = state->buildQueueItems[itemPos];
LUAU_ASSERT(!item.processing);
item.processing = true;
state->processing++;
state->executeTask(
[this, state, itemPos]()
{
performQueueItemTask(state, itemPos);
}
);
}
void Frontend::sendQueueCycleItemTask(std::shared_ptr<BuildQueueWorkState> state)
{
for (size_t i = 0; i < state->buildQueueItems.size(); i++)
{
BuildQueueItem& item = state->buildQueueItems[i];
if (!item.processing)
{
sendQueueItemTask(state, i);
break;
}
}
}
ScopePtr Frontend::getModuleEnvironment(const SourceModule& module, const Config& config, bool forAutocomplete) const
{
ScopePtr result;
@ -1130,6 +1187,12 @@ ScopePtr Frontend::getModuleEnvironment(const SourceModule& module, const Config
return result;
}
bool Frontend::allModuleDependenciesValid(const ModuleName& name, bool forAutocomplete) const
{
auto it = sourceNodes.find(name);
return it != sourceNodes.end() && !it->second->hasInvalidModuleDependency(forAutocomplete);
}
bool Frontend::isDirty(const ModuleName& name, bool forAutocomplete) const
{
auto it = sourceNodes.find(name);
@ -1144,16 +1207,35 @@ bool Frontend::isDirty(const ModuleName& name, bool forAutocomplete) const
*/
void Frontend::markDirty(const ModuleName& name, std::vector<ModuleName>* markedDirty)
{
LUAU_TIMETRACE_SCOPE("Frontend::markDirty", "Frontend");
LUAU_TIMETRACE_ARGUMENT("name", name.c_str());
traverseDependents(
name,
[markedDirty](SourceNode& sourceNode)
{
if (markedDirty)
markedDirty->push_back(sourceNode.name);
if (sourceNode.dirtySourceModule && sourceNode.dirtyModule && sourceNode.dirtyModuleForAutocomplete)
return false;
sourceNode.dirtySourceModule = true;
sourceNode.dirtyModule = true;
sourceNode.dirtyModuleForAutocomplete = true;
return true;
}
);
}
void Frontend::traverseDependents(const ModuleName& name, std::function<bool(SourceNode&)> processSubtree)
{
LUAU_TIMETRACE_SCOPE("Frontend::traverseDependents", "Frontend");
if (sourceNodes.count(name) == 0)
return;
std::unordered_map<ModuleName, std::vector<ModuleName>> reverseDeps;
for (const auto& module : sourceNodes)
{
for (const auto& dep : module.second->requireSet)
reverseDeps[dep].push_back(module.first);
}
std::vector<ModuleName> queue{name};
while (!queue.empty())
@ -1164,22 +1246,10 @@ void Frontend::markDirty(const ModuleName& name, std::vector<ModuleName>* marked
LUAU_ASSERT(sourceNodes.count(next) > 0);
SourceNode& sourceNode = *sourceNodes[next];
if (markedDirty)
markedDirty->push_back(next);
if (sourceNode.dirtySourceModule && sourceNode.dirtyModule && sourceNode.dirtyModuleForAutocomplete)
if (!processSubtree(sourceNode))
continue;
sourceNode.dirtySourceModule = true;
sourceNode.dirtyModule = true;
sourceNode.dirtyModuleForAutocomplete = true;
if (0 == reverseDeps.count(next))
continue;
sourceModules.erase(next);
const std::vector<ModuleName>& dependents = reverseDeps[next];
const Set<ModuleName>& dependents = sourceNode.dependents;
queue.insert(queue.end(), dependents.begin(), dependents.end());
}
}
@ -1207,6 +1277,7 @@ ModulePtr check(
NotNull<ModuleResolver> moduleResolver,
NotNull<FileResolver> fileResolver,
const ScopePtr& parentScope,
const ScopePtr& typeFunctionScope,
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope,
FrontendOptions options,
TypeCheckLimits limits,
@ -1223,6 +1294,7 @@ ModulePtr check(
moduleResolver,
fileResolver,
parentScope,
typeFunctionScope,
std::move(prepareModuleScope),
options,
limits,
@ -1233,7 +1305,7 @@ ModulePtr check(
struct InternalTypeFinder : TypeOnceVisitor
{
bool visit(TypeId, const ClassType&) override
bool visit(TypeId, const ExternType&) override
{
return false;
}
@ -1284,6 +1356,7 @@ ModulePtr check(
NotNull<ModuleResolver> moduleResolver,
NotNull<FileResolver> fileResolver,
const ScopePtr& parentScope,
const ScopePtr& typeFunctionScope,
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope,
FrontendOptions options,
TypeCheckLimits limits,
@ -1296,11 +1369,15 @@ ModulePtr check(
LUAU_TIMETRACE_ARGUMENT("name", sourceModule.humanReadableName.c_str());
ModulePtr result = std::make_shared<Module>();
result->checkedInNewSolver = true;
result->name = sourceModule.name;
result->humanReadableName = sourceModule.humanReadableName;
result->mode = mode;
result->internalTypes.owningModule = result.get();
result->interfaceTypes.owningModule = result.get();
result->allocator = sourceModule.allocator;
result->names = sourceModule.names;
result->root = sourceModule.root;
iceHandler->moduleName = sourceModule.name;
@ -1315,66 +1392,87 @@ ModulePtr check(
}
}
DataFlowGraph oldDfg = DataFlowGraphBuilder::build(sourceModule.root, iceHandler);
DataFlowGraph* dfgForConstraintGeneration = nullptr;
if (FFlag::LuauStoreDFGOnModule2)
{
auto [dfg, scopes] = DataFlowGraphBuilder::buildShared(sourceModule.root, iceHandler);
result->dataFlowGraph = std::move(dfg);
result->dfgScopes = std::move(scopes);
dfgForConstraintGeneration = result->dataFlowGraph.get();
}
else
{
dfgForConstraintGeneration = &oldDfg;
}
DataFlowGraph dfg = DataFlowGraphBuilder::build(sourceModule.root, NotNull{&result->defArena}, NotNull{&result->keyArena}, iceHandler);
UnifierSharedState unifierState{iceHandler};
unifierState.counters.recursionLimit = FInt::LuauTypeInferRecursionLimit;
unifierState.counters.iterationLimit = limits.unifierIterationLimit.value_or(FInt::LuauTypeInferIterationLimit);
Normalizer normalizer{&result->internalTypes, builtinTypes, NotNull{&unifierState}};
SimplifierPtr simplifier = newSimplifier(NotNull{&result->internalTypes}, builtinTypes);
TypeFunctionRuntime typeFunctionRuntime{iceHandler, NotNull{&limits}};
if (FFlag::LuauUserDefinedTypeFunctionNoEvaluation)
typeFunctionRuntime.allowEvaluation = sourceModule.parseErrors.empty();
typeFunctionRuntime.allowEvaluation = FFlag::LuauTypeFunResultInAutocomplete || sourceModule.parseErrors.empty();
ConstraintGenerator cg{
result,
NotNull{&normalizer},
NotNull{simplifier.get()},
NotNull{&typeFunctionRuntime},
moduleResolver,
builtinTypes,
iceHandler,
parentScope,
typeFunctionScope,
std::move(prepareModuleScope),
logger.get(),
NotNull{dfgForConstraintGeneration},
NotNull{&dfg},
requireCycles
};
cg.visitModuleRoot(sourceModule.root);
result->errors = std::move(cg.errors);
// FIXME: Delete this flag when clipping FFlag::DebugLuauGreedyGeneralization.
//
// This optional<> only exists so that we can run one constructor when the flag
// is set, and another when it is unset.
std::optional<ConstraintSolver> cs;
ConstraintSolver cs{
NotNull{&normalizer},
NotNull{&typeFunctionRuntime},
NotNull(cg.rootScope),
borrowConstraints(cg.constraints),
result->name,
moduleResolver,
requireCycles,
logger.get(),
NotNull{dfgForConstraintGeneration},
limits
};
if (FFlag::DebugLuauGreedyGeneralization)
{
ConstraintSet constraintSet = cg.run(sourceModule.root);
result->errors = std::move(constraintSet.errors);
cs.emplace(
NotNull{&normalizer},
NotNull{simplifier.get()},
NotNull{&typeFunctionRuntime},
result->name,
moduleResolver,
requireCycles,
logger.get(),
NotNull{&dfg},
limits,
std::move(constraintSet)
);
}
else
{
cg.visitModuleRoot(sourceModule.root);
result->errors = std::move(cg.errors);
cs.emplace(
NotNull{&normalizer},
NotNull{simplifier.get()},
NotNull{&typeFunctionRuntime},
NotNull(cg.rootScope),
borrowConstraints(cg.constraints),
NotNull{&cg.scopeToFunction},
result->name,
moduleResolver,
requireCycles,
logger.get(),
NotNull{&dfg},
limits
);
}
LUAU_ASSERT(bool(cs));
if (options.randomizeConstraintResolutionSeed)
cs.randomize(*options.randomizeConstraintResolutionSeed);
cs->randomize(*options.randomizeConstraintResolutionSeed);
try
{
cs.run();
cs->run();
}
catch (const TimeLimitError&)
{
@ -1394,12 +1492,12 @@ ModulePtr check(
printf("%s\n", output.c_str());
}
for (TypeError& e : cs.errors)
for (TypeError& e : cs->errors)
result->errors.emplace_back(std::move(e));
result->scopes = std::move(cg.scopes);
result->type = sourceModule.type;
result->upperBoundContributors = std::move(cs.upperBoundContributors);
result->upperBoundContributors = std::move(cs->upperBoundContributors);
if (result->timeout || result->cancelled)
{
@ -1419,38 +1517,30 @@ ModulePtr check(
switch (mode)
{
case Mode::Nonstrict:
if (FFlag::LuauStoreDFGOnModule2)
{
Luau::checkNonStrict(
builtinTypes,
NotNull{&typeFunctionRuntime},
iceHandler,
NotNull{&unifierState},
NotNull{dfgForConstraintGeneration},
NotNull{&limits},
sourceModule,
result.get()
);
}
else
{
Luau::checkNonStrict(
builtinTypes,
NotNull{&typeFunctionRuntime},
iceHandler,
NotNull{&unifierState},
NotNull{&oldDfg},
NotNull{&limits},
sourceModule,
result.get()
);
}
Luau::checkNonStrict(
builtinTypes,
NotNull{simplifier.get()},
NotNull{&typeFunctionRuntime},
iceHandler,
NotNull{&unifierState},
NotNull{&dfg},
NotNull{&limits},
sourceModule,
result.get()
);
break;
case Mode::Definition:
// fallthrough intentional
case Mode::Strict:
Luau::check(
builtinTypes, NotNull{&typeFunctionRuntime}, NotNull{&unifierState}, NotNull{&limits}, logger.get(), sourceModule, result.get()
builtinTypes,
NotNull{simplifier.get()},
NotNull{&typeFunctionRuntime},
NotNull{&unifierState},
NotNull{&limits},
logger.get(),
sourceModule,
result.get()
);
break;
case Mode::NoCheck:
@ -1535,6 +1625,7 @@ ModulePtr Frontend::check(
NotNull{forAutocomplete ? &moduleResolverForAutocomplete : &moduleResolver},
NotNull{fileResolver},
environmentScope ? *environmentScope : globals.globalScope,
globals.globalTypeFunctionScope,
prepareModuleScopeWrap,
options,
typeCheckLimits,
@ -1632,6 +1723,14 @@ std::pair<SourceNode*, SourceModule*> Frontend::getSourceNode(const ModuleName&
sourceNode->name = sourceModule->name;
sourceNode->humanReadableName = sourceModule->humanReadableName;
// clear all prior dependents. we will re-add them after parsing the rest of the graph
for (const auto& [moduleName, _] : sourceNode->requireLocations)
{
if (auto depIt = sourceNodes.find(moduleName); depIt != sourceNodes.end())
depIt->second->dependents.erase(sourceNode->name);
}
sourceNode->requireSet.clear();
sourceNode->requireLocations.clear();
sourceNode->dirtySourceModule = false;
@ -1753,11 +1852,13 @@ std::string FrontendModuleResolver::getHumanReadableModuleName(const ModuleName&
return frontend->fileResolver->getHumanReadableModuleName(moduleName);
}
void FrontendModuleResolver::setModule(const ModuleName& moduleName, ModulePtr module)
bool FrontendModuleResolver::setModule(const ModuleName& moduleName, ModulePtr module)
{
std::scoped_lock lock(moduleMutex);
bool replaced = modules.count(moduleName) > 0;
modules[moduleName] = std::move(module);
return replaced;
}
void FrontendModuleResolver::clearModules()

File diff suppressed because it is too large Load diff

View file

@ -9,6 +9,7 @@ GlobalTypes::GlobalTypes(NotNull<BuiltinTypes> builtinTypes)
: builtinTypes(builtinTypes)
{
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("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

@ -49,7 +49,7 @@ bool Instantiation::ignoreChildren(TypeId ty)
{
if (log->getMutable<FunctionType>(ty))
return true;
else if (get<ClassType>(ty))
else if (get<ExternType>(ty))
return true;
else
return false;
@ -60,10 +60,8 @@ TypeId Instantiation::clean(TypeId ty)
const FunctionType* ftv = log->getMutable<FunctionType>(ty);
LUAU_ASSERT(ftv);
FunctionType clone = FunctionType{level, scope, ftv->argTypes, ftv->retTypes, ftv->definition, ftv->hasSelf};
clone.magicFunction = ftv->magicFunction;
clone.dcrMagicFunction = ftv->dcrMagicFunction;
clone.dcrMagicRefinement = ftv->dcrMagicRefinement;
FunctionType clone = FunctionType{level, ftv->argTypes, ftv->retTypes, ftv->definition, ftv->hasSelf};
clone.magic = ftv->magic;
clone.tags = ftv->tags;
clone.argNames = ftv->argNames;
TypeId result = addType(std::move(clone));
@ -121,7 +119,7 @@ bool ReplaceGenerics::ignoreChildren(TypeId ty)
// whenever we quantify, so the vectors overlap if and only if they are equal.
return (!generics.empty() || !genericPacks.empty()) && (ftv->generics == generics) && (ftv->genericPacks == genericPacks);
}
else if (get<ClassType>(ty))
else if (get<ExternType>(ty))
return true;
else
{
@ -165,7 +163,7 @@ TypeId ReplaceGenerics::clean(TypeId ty)
}
else
{
return addType(FreeType{scope, level});
return arena->freshType(builtinTypes, scope, level);
}
}

View file

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

View file

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

View file

@ -17,9 +17,11 @@ LUAU_FASTINTVARIABLE(LuauSuggestionDistance, 4)
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauAttribute)
LUAU_FASTFLAG(LuauNativeAttribute)
LUAU_FASTFLAGVARIABLE(LintRedundantNativeAttribute)
LUAU_FASTFLAG(LuauDeprecatedAttribute)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
namespace Luau
{
@ -907,6 +909,11 @@ private:
return true;
}
bool visit(AstTypePack* node) override
{
return FFlag::LuauStoreReturnTypesAsPackOnAst;
}
bool visit(AstTypeReference* node) override
{
if (!node->prefix)
@ -1969,6 +1976,11 @@ private:
return true;
}
bool visit(AstTypePack* node) override
{
return FFlag::LuauStoreReturnTypesAsPackOnAst;
}
bool visit(AstTypeTable* node) override
{
if (FFlag::LuauSolverV2)
@ -2281,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
{
if (std::optional<TypeId> ty = context->getType(node->expr))
@ -2320,24 +2383,65 @@ private:
void check(AstExprIndexName* node, TypeId ty)
{
if (const ClassType* cty = get<ClassType>(ty))
if (const ExternType* cty = get<ExternType>(ty))
{
const Property* prop = lookupClassProp(cty, node->index.value);
const Property* prop = lookupExternTypeProp(cty, node->index.value);
if (prop && prop->deprecated)
report(node->location, *prop, cty->name.c_str(), node->index.value);
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))
{
auto prop = tty->props.find(node->index.value);
if (prop != tty->props.end() && prop->second.deprecated)
if (prop != tty->props.end())
{
// strip synthetic typeof() for builtin tables
if (tty->name && tty->name->compare(0, 7, "typeof(") == 0 && tty->name->back() == ')')
report(node->location, prop->second, tty->name->substr(7, tty->name->length() - 8).c_str(), node->index.value);
else
report(node->location, prop->second, tty->name ? tty->name->c_str() : nullptr, node->index.value);
if (prop->second.deprecated)
{
// strip synthetic typeof() for builtin tables
if (tty->name && tty->name->compare(0, 7, "typeof(") == 0 && tty->name->back() == ')')
report(node->location, prop->second, tty->name->substr(7, tty->name->length() - 8).c_str(), node->index.value);
else
report(node->location, prop->second, tty->name ? tty->name->c_str() : nullptr, node->index.value);
}
else if (FFlag::LuauDeprecatedAttribute)
{
if (std::optional<TypeId> ty = prop->second.readTy)
{
const FunctionType* fty = get<FunctionType>(follow(ty));
bool shouldReport = fty && fty->isDeprecatedFunction && !inScope(fty);
if (shouldReport)
{
const char* className = nullptr;
if (AstExprGlobal* global = node->expr->as<AstExprGlobal>())
className = global->name.value;
const char* functionName = node->index.value;
report(node->location, className, functionName);
}
}
}
}
}
}
@ -2356,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)
{
std::string suggestion = prop.deprecatedSuggestion.empty() ? "" : format(", use '%s' instead", prop.deprecatedSuggestion.c_str());
@ -2365,6 +2489,63 @@ private:
else
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
@ -3239,7 +3420,6 @@ static void lintComments(LintContext& context, const std::vector<HotComment>& ho
static bool hasNativeCommentDirective(const std::vector<HotComment>& hotcomments)
{
LUAU_ASSERT(FFlag::LuauNativeAttribute);
LUAU_ASSERT(FFlag::LintRedundantNativeAttribute);
for (const HotComment& hc : hotcomments)
@ -3265,7 +3445,6 @@ struct LintRedundantNativeAttribute : AstVisitor
public:
LUAU_NOINLINE static void process(LintContext& context)
{
LUAU_ASSERT(FFlag::LuauNativeAttribute);
LUAU_ASSERT(FFlag::LintRedundantNativeAttribute);
LintRedundantNativeAttribute pass;
@ -3389,7 +3568,7 @@ std::vector<LintWarning> lint(
if (context.warningEnabled(LintWarning::Code_ComparisonPrecedence))
LintComparisonPrecedence::process(context);
if (FFlag::LuauNativeAttribute && FFlag::LintRedundantNativeAttribute && context.warningEnabled(LintWarning::Code_RedundantNativeAttribute))
if (FFlag::LintRedundantNativeAttribute && context.warningEnabled(LintWarning::Code_RedundantNativeAttribute))
{
if (hasNativeCommentDirective(hotcomments))
LintRedundantNativeAttribute::process(context);

View file

@ -15,11 +15,29 @@
#include <algorithm>
LUAU_FASTFLAG(LuauSolverV2);
LUAU_DYNAMIC_FASTINT(LuauTypeSolverRelease)
LUAU_FASTFLAG(LuauRetainDefinitionAliasLocations)
namespace Luau
{
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 xml parsing done by the dcr script.
}
Luau::LogLuauProc logLuau = &defaultLogLuau;
void setLogLuau(LogLuauProc ll)
{
logLuau = ll;
}
void resetLogLuauProc()
{
logLuau = &defaultLogLuau;
}
static bool contains(Position pos, Comment comment)
{
if (comment.location.contains(pos))
@ -27,13 +45,15 @@ static bool contains(Position pos, Comment comment)
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)
// comments actually span the whole line - in incremental mode, we could pass a cursor outside of the current parsed comment range span, but it
// would still be 'within' the comment So, the cursor must be on the same line and the comment itself must come strictly after the `begin`
else if (comment.type == Lexeme::Comment && comment.location.end.line == pos.line && comment.location.begin <= pos)
return true;
else
return false;
}
static bool isWithinComment(const std::vector<Comment>& commentLocations, Position pos)
bool isWithinComment(const std::vector<Comment>& commentLocations, Position pos)
{
auto iter = std::lower_bound(
commentLocations.begin(),
@ -41,6 +61,8 @@ static bool isWithinComment(const std::vector<Comment>& commentLocations, Positi
Comment{Lexeme::Comment, Location{pos, pos}},
[](const Comment& a, const Comment& b)
{
if (a.type == Lexeme::Comment)
return a.location.end.line < b.location.end.line;
return a.location.end < b.location.end;
}
);
@ -132,34 +154,25 @@ struct ClonePublicInterface : Substitution
}
ftv->level = TypeLevel{0, 0};
if (FFlag::LuauSolverV2 && DFInt::LuauTypeSolverRelease >= 645)
ftv->scope = nullptr;
}
else if (TableType* ttv = getMutable<TableType>(result))
{
ttv->level = TypeLevel{0, 0};
if (FFlag::LuauSolverV2 && DFInt::LuauTypeSolverRelease >= 645)
if (FFlag::LuauSolverV2)
ttv->scope = nullptr;
}
if (FFlag::LuauSolverV2 && DFInt::LuauTypeSolverRelease >= 645)
if (FFlag::LuauSolverV2)
{
if (auto freety = getMutable<FreeType>(result))
{
if (DFInt::LuauTypeSolverRelease >= 646)
{
module->errors.emplace_back(
freety->scope->location,
module->name,
InternalError{"Free type is escaping its module; please report this bug at "
"https://github.com/luau-lang/luau/issues"}
);
result = builtinTypes->errorRecoveryType();
}
else
{
freety->scope = nullptr;
}
module->errors.emplace_back(
freety->scope->location,
module->name,
InternalError{"Free type is escaping its module; please report this bug at "
"https://github.com/luau-lang/luau/issues"}
);
result = builtinTypes->errorRecoveryType();
}
else if (auto genericty = getMutable<GenericType>(result))
{
@ -172,26 +185,18 @@ struct ClonePublicInterface : Substitution
TypePackId clean(TypePackId tp) override
{
if (FFlag::LuauSolverV2 && DFInt::LuauTypeSolverRelease >= 645)
if (FFlag::LuauSolverV2)
{
auto clonedTp = clone(tp);
if (auto ftp = getMutable<FreeTypePack>(clonedTp))
{
if (DFInt::LuauTypeSolverRelease >= 646)
{
module->errors.emplace_back(
ftp->scope->location,
module->name,
InternalError{"Free type pack is escaping its module; please report this bug at "
"https://github.com/luau-lang/luau/issues"}
);
clonedTp = builtinTypes->errorRecoveryTypePack();
}
else
{
ftp->scope = nullptr;
}
module->errors.emplace_back(
ftp->scope->location,
module->name,
InternalError{"Free type pack is escaping its module; please report this bug at "
"https://github.com/luau-lang/luau/issues"}
);
clonedTp = builtinTypes->errorRecoveryTypePack();
}
else if (auto gtp = getMutable<GenericTypePack>(clonedTp))
gtp->scope = nullptr;
@ -260,7 +265,10 @@ struct ClonePublicInterface : Substitution
TypeId type = cloneType(tf.type);
return TypeFun{typeParams, typePackParams, type};
if (FFlag::LuauRetainDefinitionAliasLocations)
return TypeFun{typeParams, typePackParams, type, tf.definitionLocation};
else
return TypeFun{typeParams, typePackParams, type};
}
};

File diff suppressed because it is too large Load diff

View file

@ -17,12 +17,12 @@
LUAU_FASTFLAGVARIABLE(DebugLuauCheckNormalizeInvariant)
LUAU_FASTINTVARIABLE(LuauNormalizeCacheLimit, 100000);
LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTINTVARIABLE(LuauNormalizeCacheLimit, 100000)
LUAU_FASTINTVARIABLE(LuauNormalizeIntersectionLimit, 200)
LUAU_FASTFLAGVARIABLE(LuauNormalizationTracksCyclicPairsThroughInhabitance);
LUAU_FASTFLAGVARIABLE(LuauIntersectNormalsNeedsToTrackResourceLimits);
LUAU_FASTINTVARIABLE(LuauNormalizeUnionLimit, 100)
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAGVARIABLE(LuauFixInfiniteRecursionInNormalization)
LUAU_FASTFLAGVARIABLE(LuauNormalizationCatchMetatableCycles)
namespace Luau
{
@ -249,23 +249,23 @@ bool isSubtype(const NormalizedStringType& subStr, const NormalizedStringType& s
return true;
}
void NormalizedClassType::pushPair(TypeId ty, TypeIds negations)
void NormalizedExternType::pushPair(TypeId ty, TypeIds negations)
{
auto result = classes.insert(std::make_pair(ty, std::move(negations)));
auto result = externTypes.insert(std::make_pair(ty, std::move(negations)));
if (result.second)
ordering.push_back(ty);
LUAU_ASSERT(ordering.size() == classes.size());
LUAU_ASSERT(ordering.size() == externTypes.size());
}
void NormalizedClassType::resetToNever()
void NormalizedExternType::resetToNever()
{
ordering.clear();
classes.clear();
externTypes.clear();
}
bool NormalizedClassType::isNever() const
bool NormalizedExternType::isNever() const
{
return classes.empty();
return externTypes.empty();
}
void NormalizedFunctionType::resetToTop()
@ -304,17 +304,17 @@ bool NormalizedType::isUnknown() const
// Otherwise, we can still be unknown!
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
bool isTopClass = false;
for (auto [t, disj] : classes.classes)
bool isTopExternType = false;
for (const auto& [t, disj] : externTypes.externTypes)
{
if (auto ct = get<ClassType>(t))
if (auto ct = get<ExternType>(t))
{
if (ct->name == "class" && disj.empty())
{
isTopClass = true;
isTopExternType = true;
break;
}
}
@ -330,24 +330,24 @@ bool NormalizedType::isUnknown() const
}
}
// any = unknown or error ==> we need to make sure we have all the unknown components, but not errors
return get<NeverType>(errors) && hasAllPrimitives && isTopClass && isTopTable && functions.isTop;
return get<NeverType>(errors) && hasAllPrimitives && isTopExternType && isTopTable && functions.isTop;
}
bool NormalizedType::isExactlyNumber() const
{
return hasNumbers() && !hasTops() && !hasBooleans() && !hasClasses() && !hasErrors() && !hasNils() && !hasStrings() && !hasThreads() &&
return hasNumbers() && !hasTops() && !hasBooleans() && !hasExternTypes() && !hasErrors() && !hasNils() && !hasStrings() && !hasThreads() &&
!hasBuffers() && !hasTables() && !hasFunctions() && !hasTyvars();
}
bool NormalizedType::isSubtypeOfString() const
{
return hasStrings() && !hasTops() && !hasBooleans() && !hasClasses() && !hasErrors() && !hasNils() && !hasNumbers() && !hasThreads() &&
return hasStrings() && !hasTops() && !hasBooleans() && !hasExternTypes() && !hasErrors() && !hasNils() && !hasNumbers() && !hasThreads() &&
!hasBuffers() && !hasTables() && !hasFunctions() && !hasTyvars();
}
bool NormalizedType::isSubtypeOfBooleans() const
{
return hasBooleans() && !hasTops() && !hasClasses() && !hasErrors() && !hasNils() && !hasNumbers() && !hasStrings() && !hasThreads() &&
return hasBooleans() && !hasTops() && !hasExternTypes() && !hasErrors() && !hasNils() && !hasNumbers() && !hasStrings() && !hasThreads() &&
!hasBuffers() && !hasTables() && !hasFunctions() && !hasTyvars();
}
@ -380,9 +380,9 @@ bool NormalizedType::hasBooleans() const
return !get<NeverType>(booleans);
}
bool NormalizedType::hasClasses() const
bool NormalizedType::hasExternTypes() const
{
return !classes.isNever();
return !externTypes.isNever();
}
bool NormalizedType::hasErrors() const
@ -440,7 +440,7 @@ bool NormalizedType::isFalsy() const
hasAFalse = !bs->value;
}
return (hasAFalse || hasNils()) && (!hasTops() && !hasClasses() && !hasErrors() && !hasNumbers() && !hasStrings() && !hasThreads() &&
return (hasAFalse || hasNils()) && (!hasTops() && !hasExternTypes() && !hasErrors() && !hasNumbers() && !hasStrings() && !hasThreads() &&
!hasBuffers() && !hasTables() && !hasFunctions() && !hasTyvars());
}
@ -452,7 +452,7 @@ bool NormalizedType::isTruthy() const
static bool isShallowInhabited(const NormalizedType& norm)
{
// This test is just a shallow check, for example it returns `true` for `{ p : never }`
return !get<NeverType>(norm.tops) || !get<NeverType>(norm.booleans) || !norm.classes.isNever() || !get<NeverType>(norm.errors) ||
return !get<NeverType>(norm.tops) || !get<NeverType>(norm.booleans) || !norm.externTypes.isNever() || !get<NeverType>(norm.errors) ||
!get<NeverType>(norm.nils) || !get<NeverType>(norm.numbers) || !norm.strings.isNever() || !get<NeverType>(norm.threads) ||
!get<NeverType>(norm.buffers) || !norm.functions.isNever() || !norm.tables.empty() || !norm.tyvars.empty();
}
@ -471,7 +471,7 @@ NormalizationResult Normalizer::isInhabited(const NormalizedType* norm, Set<Type
return NormalizationResult::HitLimits;
if (!get<NeverType>(norm->tops) || !get<NeverType>(norm->booleans) || !get<NeverType>(norm->errors) || !get<NeverType>(norm->nils) ||
!get<NeverType>(norm->numbers) || !get<NeverType>(norm->threads) || !get<NeverType>(norm->buffers) || !norm->classes.isNever() ||
!get<NeverType>(norm->numbers) || !get<NeverType>(norm->threads) || !get<NeverType>(norm->buffers) || !norm->externTypes.isNever() ||
!norm->strings.isNever() || !norm->functions.isNever())
return NormalizationResult::True;
@ -579,7 +579,7 @@ NormalizationResult Normalizer::isIntersectionInhabited(TypeId left, TypeId righ
{
left = follow(left);
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)
{
@ -619,13 +619,13 @@ static int tyvarIndex(TypeId ty)
return 0;
}
static bool isTop(NotNull<BuiltinTypes> builtinTypes, const NormalizedClassType& classes)
static bool isTop(NotNull<BuiltinTypes> builtinTypes, const NormalizedExternType& externTypes)
{
if (classes.classes.size() != 1)
if (externTypes.externTypes.size() != 1)
return false;
auto first = classes.classes.begin();
if (first->first != builtinTypes->classType)
auto first = externTypes.externTypes.begin();
if (first->first != builtinTypes->externType)
return false;
if (!first->second.empty())
@ -634,11 +634,11 @@ static bool isTop(NotNull<BuiltinTypes> builtinTypes, const NormalizedClassType&
return true;
}
static void resetToTop(NotNull<BuiltinTypes> builtinTypes, NormalizedClassType& classes)
static void resetToTop(NotNull<BuiltinTypes> builtinTypes, NormalizedExternType& externTypes)
{
classes.ordering.clear();
classes.classes.clear();
classes.pushPair(builtinTypes->classType, TypeIds{});
externTypes.ordering.clear();
externTypes.externTypes.clear();
externTypes.pushPair(builtinTypes->externType, TypeIds{});
}
#ifdef LUAU_ASSERTENABLED
@ -762,50 +762,50 @@ static bool areNormalizedTables(const TypeIds& tys)
return true;
}
static bool areNormalizedClasses(const NormalizedClassType& tys)
static bool areNormalizedExternTypes(const NormalizedExternType& tys)
{
for (const auto& [ty, negations] : tys.classes)
for (const auto& [ty, negations] : tys.externTypes)
{
const ClassType* ctv = get<ClassType>(ty);
if (!ctv)
const ExternType* etv = get<ExternType>(ty);
if (!etv)
{
return false;
}
for (TypeId negation : negations)
{
const ClassType* nctv = get<ClassType>(negation);
const ExternType* nctv = get<ExternType>(negation);
if (!nctv)
{
return false;
}
if (!isSubclass(nctv, ctv))
if (!isSubclass(nctv, etv))
{
return false;
}
}
for (const auto& [otherTy, otherNegations] : tys.classes)
for (const auto& [otherTy, otherNegations] : tys.externTypes)
{
if (otherTy == ty)
continue;
const ClassType* octv = get<ClassType>(otherTy);
const ExternType* octv = get<ExternType>(otherTy);
if (!octv)
{
return false;
}
if (isSubclass(ctv, octv))
if (isSubclass(etv, octv))
{
auto iss = [ctv](TypeId t)
auto iss = [etv](TypeId t)
{
const ClassType* c = get<ClassType>(t);
const ExternType* c = get<ExternType>(t);
if (!c)
return false;
return isSubclass(ctv, c);
return isSubclass(etv, c);
};
if (!std::any_of(otherNegations.begin(), otherNegations.end(), iss))
@ -847,7 +847,7 @@ static void assertInvariant(const NormalizedType& norm)
LUAU_ASSERT(isNormalizedTop(norm.tops));
LUAU_ASSERT(isNormalizedBoolean(norm.booleans));
LUAU_ASSERT(areNormalizedClasses(norm.classes));
LUAU_ASSERT(areNormalizedExternTypes(norm.externTypes));
LUAU_ASSERT(isNormalizedError(norm.errors));
LUAU_ASSERT(isNormalizedNil(norm.nils));
LUAU_ASSERT(isNormalizedNumber(norm.numbers));
@ -988,7 +988,7 @@ void Normalizer::clearNormal(NormalizedType& norm)
{
norm.tops = builtinTypes->neverType;
norm.booleans = builtinTypes->neverType;
norm.classes.resetToNever();
norm.externTypes.resetToNever();
norm.errors = builtinTypes->neverType;
norm.nils = builtinTypes->neverType;
norm.numbers = builtinTypes->neverType;
@ -1138,17 +1138,17 @@ TypeId Normalizer::unionOfBools(TypeId here, TypeId there)
return builtinTypes->booleanType;
}
void Normalizer::unionClassesWithClass(TypeIds& heres, TypeId there)
void Normalizer::unionExternTypesWithExternType(TypeIds& heres, TypeId there)
{
if (heres.count(there))
return;
const ClassType* tctv = get<ClassType>(there);
const ExternType* tctv = get<ExternType>(there);
for (auto it = heres.begin(); it != heres.end();)
{
TypeId here = *it;
const ClassType* hctv = get<ClassType>(here);
const ExternType* hctv = get<ExternType>(here);
if (isSubclass(tctv, hctv))
return;
else if (isSubclass(hctv, tctv))
@ -1160,16 +1160,16 @@ void Normalizer::unionClassesWithClass(TypeIds& heres, TypeId there)
heres.insert(there);
}
void Normalizer::unionClasses(TypeIds& heres, const TypeIds& theres)
void Normalizer::unionExternTypes(TypeIds& heres, const TypeIds& theres)
{
for (TypeId there : theres)
unionClassesWithClass(heres, there);
unionExternTypesWithExternType(heres, there);
}
static bool isSubclass(TypeId test, TypeId parent)
{
const ClassType* testCtv = get<ClassType>(test);
const ClassType* parentCtv = get<ClassType>(parent);
const ExternType* testCtv = get<ExternType>(test);
const ExternType* parentCtv = get<ExternType>(parent);
LUAU_ASSERT(testCtv);
LUAU_ASSERT(parentCtv);
@ -1177,12 +1177,12 @@ static bool isSubclass(TypeId test, TypeId parent)
return isSubclass(testCtv, parentCtv);
}
void Normalizer::unionClassesWithClass(NormalizedClassType& heres, TypeId there)
void Normalizer::unionExternTypesWithExternType(NormalizedExternType& heres, TypeId there)
{
for (auto it = heres.ordering.begin(); it != heres.ordering.end();)
{
TypeId hereTy = *it;
TypeIds& hereNegations = heres.classes.at(hereTy);
TypeIds& hereNegations = heres.externTypes.at(hereTy);
// If the incoming class is a subclass of another class in the map, we
// must ensure that it is negated by one of the negations in the same
@ -1204,7 +1204,7 @@ void Normalizer::unionClassesWithClass(NormalizedClassType& heres, TypeId there)
}
// If the incoming class is a superclass of one of the
// negations, then the negation no longer applies and must be
// removed. This is also true if they are equal. Since classes
// removed. This is also true if they are equal. Since extern types
// are, at this time, entirely persistent (we do not clone
// them), a pointer identity check is sufficient.
else if (isSubclass(hereNegation, there))
@ -1231,7 +1231,7 @@ void Normalizer::unionClassesWithClass(NormalizedClassType& heres, TypeId there)
{
TypeIds negations = std::move(hereNegations);
it = heres.ordering.erase(it);
heres.classes.erase(hereTy);
heres.externTypes.erase(hereTy);
heres.pushPair(there, std::move(negations));
return;
@ -1248,10 +1248,10 @@ void Normalizer::unionClassesWithClass(NormalizedClassType& heres, TypeId there)
heres.pushPair(there, TypeIds{});
}
void Normalizer::unionClasses(NormalizedClassType& heres, const NormalizedClassType& theres)
void Normalizer::unionExternTypes(NormalizedExternType& heres, const NormalizedExternType& theres)
{
// This method bears much similarity with unionClassesWithClass, but is
// solving a more general problem. In unionClassesWithClass, we are dealing
// This method bears much similarity with unionExternTypesWithExternType, but is
// solving a more general problem. In unionExternTypesWithExternType, we are dealing
// with a singular positive type. Since it's one type, we can use early
// returns as control flow. Since it's guaranteed to be positive, we do not
// have negations to worry about combining. The two aspects combine to make
@ -1260,9 +1260,9 @@ void Normalizer::unionClasses(NormalizedClassType& heres, const NormalizedClassT
for (const TypeId thereTy : theres.ordering)
{
const TypeIds& thereNegations = theres.classes.at(thereTy);
const TypeIds& thereNegations = theres.externTypes.at(thereTy);
// If it happens that there are _no_ classes in the current map, or the
// If it happens that there are _no_ extern types in the current map, or the
// incoming class is completely unrelated to any class in the current
// map, we must insert the incoming pair as-is.
bool insert = true;
@ -1270,7 +1270,7 @@ void Normalizer::unionClasses(NormalizedClassType& heres, const NormalizedClassT
for (auto it = heres.ordering.begin(); it != heres.ordering.end();)
{
TypeId hereTy = *it;
TypeIds& hereNegations = heres.classes.at(hereTy);
TypeIds& hereNegations = heres.externTypes.at(hereTy);
if (isSubclass(thereTy, hereTy))
{
@ -1294,7 +1294,7 @@ void Normalizer::unionClasses(NormalizedClassType& heres, const NormalizedClassT
// If the incoming class is a superclass of one of the
// negations, then the negation no longer applies and must
// be removed. This is also true if they are equal. Since
// classes are, at this time, entirely persistent (we do not
// extern types are, at this time, entirely persistent (we do not
// clone them), a pointer identity check is sufficient.
else if (isSubclass(hereNegateTy, thereTy))
{
@ -1319,17 +1319,17 @@ void Normalizer::unionClasses(NormalizedClassType& heres, const NormalizedClassT
else if (isSubclass(hereTy, thereTy))
{
TypeIds negations = std::move(hereNegations);
unionClasses(negations, thereNegations);
unionExternTypes(negations, thereNegations);
it = heres.ordering.erase(it);
heres.classes.erase(hereTy);
heres.externTypes.erase(hereTy);
heres.pushPair(thereTy, std::move(negations));
insert = false;
break;
}
else if (hereTy == thereTy)
{
unionClasses(hereNegations, thereNegations);
unionExternTypes(hereNegations, thereNegations);
insert = false;
break;
}
@ -1685,8 +1685,12 @@ NormalizationResult Normalizer::unionNormals(NormalizedType& here, const Normali
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);
unionClasses(here.classes, there.classes);
unionExternTypes(here.externTypes, there.externTypes);
here.errors = (get<NeverType>(there.errors) ? here.errors : there.errors);
here.nils = (get<NeverType>(there.nils) ? here.nils : there.nils);
@ -1696,6 +1700,7 @@ NormalizationResult Normalizer::unionNormals(NormalizedType& here, const Normali
here.buffers = (get<NeverType>(there.buffers) ? here.buffers : there.buffers);
unionFunctions(here.functions, there.functions);
unionTables(here.tables, there.tables);
return NormalizationResult::True;
}
@ -1735,7 +1740,7 @@ NormalizationResult Normalizer::intersectNormalWithNegationTy(TypeId toNegate, N
return NormalizationResult::True;
}
// See above for an explaination of `ignoreSmallerTyvars`.
// See above for an explanation of `ignoreSmallerTyvars`.
NormalizationResult Normalizer::unionNormalWithTy(
NormalizedType& here,
TypeId there,
@ -1809,7 +1814,8 @@ NormalizationResult Normalizer::unionNormalWithTy(
}
else if (get<UnknownType>(here.tops))
return NormalizationResult::True;
else if (get<GenericType>(there) || get<FreeType>(there) || get<BlockedType>(there) || get<PendingExpansionType>(there) || get<TypeFunctionInstanceType>(there))
else if (get<GenericType>(there) || get<FreeType>(there) || get<BlockedType>(there) || get<PendingExpansionType>(there) ||
get<TypeFunctionInstanceType>(there))
{
if (tyvarIndex(there) <= ignoreSmallerTyvars)
return NormalizationResult::True;
@ -1824,8 +1830,8 @@ NormalizationResult Normalizer::unionNormalWithTy(
unionFunctionsWithFunction(here.functions, there);
else if (get<TableType>(there) || get<MetatableType>(there))
unionTablesWithTable(here.tables, there);
else if (get<ClassType>(there))
unionClassesWithClass(here.classes, there);
else if (get<ExternType>(there))
unionExternTypesWithExternType(here.externTypes, there);
else if (get<ErrorType>(there))
here.errors = there;
else if (const PrimitiveType* ptv = get<PrimitiveType>(there))
@ -1938,29 +1944,29 @@ std::optional<NormalizedType> Normalizer::negateNormal(const NormalizedType& her
result.booleans = builtinTypes->trueType;
}
if (here.classes.isNever())
if (here.externTypes.isNever())
{
resetToTop(builtinTypes, result.classes);
resetToTop(builtinTypes, result.externTypes);
}
else if (isTop(builtinTypes, result.classes))
else if (isTop(builtinTypes, result.externTypes))
{
result.classes.resetToNever();
result.externTypes.resetToNever();
}
else
{
TypeIds rootNegations{};
for (const auto& [hereParent, hereNegations] : here.classes.classes)
for (const auto& [hereParent, hereNegations] : here.externTypes.externTypes)
{
if (hereParent != builtinTypes->classType)
if (hereParent != builtinTypes->externType)
rootNegations.insert(hereParent);
for (TypeId hereNegation : hereNegations)
unionClassesWithClass(result.classes, hereNegation);
unionExternTypesWithExternType(result.externTypes, hereNegation);
}
if (!rootNegations.empty())
result.classes.pushPair(builtinTypes->classType, rootNegations);
result.externTypes.pushPair(builtinTypes->externType, rootNegations);
}
result.nils = get<NeverType>(here.nils) ? builtinTypes->nilType : builtinTypes->neverType;
@ -2138,7 +2144,7 @@ TypeId Normalizer::intersectionOfBools(TypeId here, TypeId there)
return there;
}
void Normalizer::intersectClasses(NormalizedClassType& heres, const NormalizedClassType& theres)
void Normalizer::intersectExternTypes(NormalizedExternType& heres, const NormalizedExternType& theres)
{
if (theres.isNever())
{
@ -2172,12 +2178,12 @@ void Normalizer::intersectClasses(NormalizedClassType& heres, const NormalizedCl
// declare the result of the intersection operation to be never.
for (const TypeId thereTy : theres.ordering)
{
const TypeIds& thereNegations = theres.classes.at(thereTy);
const TypeIds& thereNegations = theres.externTypes.at(thereTy);
for (auto it = heres.ordering.begin(); it != heres.ordering.end();)
{
TypeId hereTy = *it;
TypeIds& hereNegations = heres.classes.at(hereTy);
TypeIds& hereNegations = heres.externTypes.at(hereTy);
if (isSubclass(thereTy, hereTy))
{
@ -2200,10 +2206,10 @@ void Normalizer::intersectClasses(NormalizedClassType& heres, const NormalizedCl
}
}
unionClasses(negations, thereNegations);
unionExternTypes(negations, thereNegations);
it = heres.ordering.erase(it);
heres.classes.erase(hereTy);
heres.externTypes.erase(hereTy);
heres.pushPair(thereTy, std::move(negations));
break;
}
@ -2228,15 +2234,15 @@ void Normalizer::intersectClasses(NormalizedClassType& heres, const NormalizedCl
{
if (isSubclass(hereTy, *nIt))
{
// eg SomeClass & (class & ~SomeClass)
// or SomeClass & (class & ~ParentClass)
heres.classes.erase(hereTy);
// eg SomeExternType & (class & ~SomeExternType)
// or SomeExternType & (class & ~ParentExternType)
heres.externTypes.erase(hereTy);
it = heres.ordering.erase(it);
erasedHere = true;
break;
}
// eg SomeClass & (class & ~Unrelated)
// eg SomeExternType & (class & ~Unrelated)
if (!isSubclass(*nIt, hereTy))
nIt = negations.erase(nIt);
else
@ -2245,30 +2251,30 @@ void Normalizer::intersectClasses(NormalizedClassType& heres, const NormalizedCl
if (!erasedHere)
{
unionClasses(hereNegations, negations);
unionExternTypes(hereNegations, negations);
++it;
}
}
else if (hereTy == thereTy)
{
unionClasses(hereNegations, thereNegations);
unionExternTypes(hereNegations, thereNegations);
break;
}
else
{
it = heres.ordering.erase(it);
heres.classes.erase(hereTy);
heres.externTypes.erase(hereTy);
}
}
}
}
void Normalizer::intersectClassesWithClass(NormalizedClassType& heres, TypeId there)
void Normalizer::intersectExternTypesWithExternType(NormalizedExternType& heres, TypeId there)
{
for (auto it = heres.ordering.begin(); it != heres.ordering.end();)
{
TypeId hereTy = *it;
const TypeIds& hereNegations = heres.classes.at(hereTy);
const TypeIds& hereNegations = heres.externTypes.at(hereTy);
// If the incoming class _is_ the current class, we skip it. Maybe
// another entry will have a different story. We check for this first
@ -2284,9 +2290,24 @@ void Normalizer::intersectClassesWithClass(NormalizedClassType& heres, TypeId th
else if (isSubclass(there, hereTy))
{
TypeIds negations = std::move(hereNegations);
bool emptyIntersectWithNegation = false;
for (auto nIt = negations.begin(); nIt != negations.end();)
{
if (isSubclass(there, *nIt))
{
// Hitting this block means that the incoming class is a
// subclass of this type, _and_ one of its negations is a
// superclass of this type, e.g.:
//
// Dog & ~Animal
//
// Clearly this intersects to never, so we mark this class as
// being removed from the normalized class type.
emptyIntersectWithNegation = true;
break;
}
if (!isSubclass(*nIt, there))
{
nIt = negations.erase(nIt);
@ -2298,8 +2319,9 @@ void Normalizer::intersectClassesWithClass(NormalizedClassType& heres, TypeId th
}
it = heres.ordering.erase(it);
heres.classes.erase(hereTy);
heres.pushPair(there, std::move(negations));
heres.externTypes.erase(hereTy);
if (!emptyIntersectWithNegation)
heres.pushPair(there, std::move(negations));
break;
}
// If the incoming class is a superclass of the current class, we don't
@ -2313,7 +2335,7 @@ void Normalizer::intersectClassesWithClass(NormalizedClassType& heres, TypeId th
else
{
it = heres.ordering.erase(it);
heres.classes.erase(hereTy);
heres.externTypes.erase(hereTy);
}
}
}
@ -2584,11 +2606,31 @@ std::optional<TypeId> Normalizer::intersectionOfTables(TypeId here, TypeId there
{
if (tprop.readTy.has_value())
{
// if the intersection of the read types of a property is uninhabited, the whole table is `never`.
// We've seen these table prop elements before and we're about to ask if their intersection
// is inhabited
if (FFlag::LuauNormalizationTracksCyclicPairsThroughInhabitance)
if (FFlag::LuauFixInfiniteRecursionInNormalization)
{
TypeId ty = simplifyIntersection(builtinTypes, NotNull{arena}, *hprop.readTy, *tprop.readTy).result;
// If any property is going to get mapped to `never`, we can just call the entire table `never`.
// Since this check is syntactic, we may sometimes miss simplifying tables with complex uninhabited properties.
// Prior versions of this code attempted to do this semantically using the normalization machinery, but this
// mistakenly causes infinite loops when giving more complex recursive table types. As it stands, this approach
// will continue to scale as simplification is improved, but we may wish to reintroduce the semantic approach
// once we have revisited the usage of seen sets systematically (and possibly with some additional guarding to recognize
// when types are infinitely-recursive with non-pointer identical instances of them, or some guard to prevent that
// construction altogether). See also: `gh1632_no_infinite_recursion_in_normalization`
if (get<NeverType>(ty))
return {builtinTypes->neverType};
prop.readTy = ty;
hereSubThere &= (ty == hprop.readTy);
thereSubHere &= (ty == tprop.readTy);
}
else
{
// if the intersection of the read types of a property is uninhabited, the whole table is `never`.
// We've seen these table prop elements before and we're about to ask if their intersection
// is inhabited
auto pair1 = std::pair{*hprop.readTy, *tprop.readTy};
auto pair2 = std::pair{*tprop.readTy, *hprop.readTy};
if (seenTablePropPairs.contains(pair1) || seenTablePropPairs.contains(pair2))
@ -2603,6 +2645,8 @@ std::optional<TypeId> Normalizer::intersectionOfTables(TypeId here, TypeId there
seenTablePropPairs.insert(pair2);
}
// FIXME(ariel): this is being added in a flag removal, so not changing the semantics here, but worth noting that this
// fresh `seenSet` is definitely a bug. we already have `seenSet` from the parameter that _should_ have been used here.
Set<TypeId> seenSet{nullptr};
NormalizationResult res = isIntersectionInhabited(*hprop.readTy, *tprop.readTy, seenTablePropPairs, seenSet);
@ -2616,34 +2660,6 @@ std::optional<TypeId> Normalizer::intersectionOfTables(TypeId here, TypeId there
hereSubThere &= (ty == hprop.readTy);
thereSubHere &= (ty == tprop.readTy);
}
else
{
if (seenSet.contains(*hprop.readTy) && seenSet.contains(*tprop.readTy))
{
seenSet.erase(*hprop.readTy);
seenSet.erase(*tprop.readTy);
return {builtinTypes->neverType};
}
else
{
seenSet.insert(*hprop.readTy);
seenSet.insert(*tprop.readTy);
}
NormalizationResult res = isIntersectionInhabited(*hprop.readTy, *tprop.readTy);
seenSet.erase(*hprop.readTy);
seenSet.erase(*tprop.readTy);
if (NormalizationResult::True != res)
return {builtinTypes->neverType};
TypeId ty = simplifyIntersection(builtinTypes, NotNull{arena}, *hprop.readTy, *tprop.readTy).result;
prop.readTy = ty;
hereSubThere &= (ty == hprop.readTy);
thereSubHere &= (ty == tprop.readTy);
}
}
else
{
@ -3039,15 +3055,12 @@ NormalizationResult Normalizer::intersectTyvarsWithTy(
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)
{
if (FFlag::LuauIntersectNormalsNeedsToTrackResourceLimits)
{
RecursionCounter _rc(&sharedState->counters.recursionCount);
if (!withinResourceLimits())
return NormalizationResult::HitLimits;
}
RecursionCounter _rc(&sharedState->counters.recursionCount);
if (!withinResourceLimits())
return NormalizationResult::HitLimits;
if (!get<NeverType>(there.tops))
{
@ -3060,14 +3073,17 @@ NormalizationResult Normalizer::intersectNormals(NormalizedType& here, const Nor
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
if (here.tables.size() * there.tables.size() >= size_t(FInt::LuauNormalizeIntersectionLimit))
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);
intersectClasses(here.classes, there.classes);
intersectExternTypes(here.externTypes, there.externTypes);
here.errors = (get<NeverType>(there.errors) ? there.errors : here.errors);
here.nils = (get<NeverType>(there.nils) ? there.nils : here.nils);
here.numbers = (get<NeverType>(there.numbers) ? there.numbers : here.numbers);
@ -3162,7 +3178,8 @@ NormalizationResult Normalizer::intersectNormalWithTy(
}
return NormalizationResult::True;
}
else if (get<GenericType>(there) || get<FreeType>(there) || get<BlockedType>(there) || get<PendingExpansionType>(there) || get<TypeFunctionInstanceType>(there))
else if (get<GenericType>(there) || get<FreeType>(there) || get<BlockedType>(there) || get<PendingExpansionType>(there) ||
get<TypeFunctionInstanceType>(there))
{
NormalizedType thereNorm{builtinTypes};
NormalizedType topNorm{builtinTypes};
@ -3188,18 +3205,18 @@ NormalizationResult Normalizer::intersectNormalWithTy(
intersectTablesWithTable(tables, there, seenTablePropPairs, seenSetTypes);
here.tables = std::move(tables);
}
else if (get<ClassType>(there))
else if (get<ExternType>(there))
{
NormalizedClassType nct = std::move(here.classes);
NormalizedExternType nct = std::move(here.externTypes);
clearNormal(here);
intersectClassesWithClass(nct, there);
here.classes = std::move(nct);
intersectExternTypesWithExternType(nct, there);
here.externTypes = std::move(nct);
}
else if (get<ErrorType>(there))
{
TypeId errors = here.errors;
clearNormal(here);
here.errors = errors;
here.errors = get<ErrorType>(errors) ? errors : there;
}
else if (const PrimitiveType* ptv = get<PrimitiveType>(there))
{
@ -3257,7 +3274,7 @@ NormalizationResult Normalizer::intersectNormalWithTy(
subtractPrimitive(here, ntv->ty);
else if (const SingletonType* stv = get<SingletonType>(t))
subtractSingleton(here, follow(ntv->ty));
else if (get<ClassType>(t))
else if (get<ExternType>(t))
{
NormalizationResult res = intersectNormalWithNegationTy(t, here);
if (shouldEarlyExit(res))
@ -3296,8 +3313,18 @@ NormalizationResult Normalizer::intersectNormalWithTy(
clearNormal(here);
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))
{
here.tyvars = std::move(tyvars);
return intersectNormalWithTy(here, nt->ty, seenTablePropPairs, seenSetTypes);
}
else
{
// TODO negated unions, intersections, table, and function.
@ -3307,7 +3334,7 @@ NormalizationResult Normalizer::intersectNormalWithTy(
}
else if (get<NeverType>(there))
{
here.classes.resetToNever();
here.externTypes.resetToNever();
}
else if (get<NoRefineType>(there))
{
@ -3325,7 +3352,7 @@ NormalizationResult Normalizer::intersectNormalWithTy(
return NormalizationResult::True;
}
void makeTableShared(TypeId ty)
void makeTableShared_DEPRECATED(TypeId ty)
{
ty = follow(ty);
if (auto tableTy = getMutable<TableType>(ty))
@ -3335,11 +3362,35 @@ void makeTableShared(TypeId ty)
}
else if (auto metatableTy = get<MetatableType>(ty))
{
makeTableShared(metatableTy->metatable);
makeTableShared(metatableTy->table);
makeTableShared_DEPRECATED(metatableTy->metatable);
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
TypeId Normalizer::typeFromNormal(const NormalizedType& norm)
{
@ -3352,18 +3403,18 @@ TypeId Normalizer::typeFromNormal(const NormalizedType& norm)
if (!get<NeverType>(norm.booleans))
result.push_back(norm.booleans);
if (isTop(builtinTypes, norm.classes))
if (isTop(builtinTypes, norm.externTypes))
{
result.push_back(builtinTypes->classType);
result.push_back(builtinTypes->externType);
}
else if (!norm.classes.isNever())
else if (!norm.externTypes.isNever())
{
std::vector<TypeId> parts;
parts.reserve(norm.classes.classes.size());
parts.reserve(norm.externTypes.externTypes.size());
for (const TypeId normTy : norm.classes.ordering)
for (const TypeId normTy : norm.externTypes.ordering)
{
const TypeIds& normNegations = norm.classes.classes.at(normTy);
const TypeIds& normNegations = norm.externTypes.externTypes.at(normTy);
if (normNegations.empty())
{
@ -3439,7 +3490,10 @@ TypeId Normalizer::typeFromNormal(const NormalizedType& norm)
result.reserve(result.size() + norm.tables.size());
for (auto table : norm.tables)
{
makeTableShared(table);
if (FFlag::LuauNormalizationCatchMetatableCycles)
makeTableShared(table);
else
makeTableShared_DEPRECATED(table);
result.push_back(table);
}
}
@ -3465,7 +3519,14 @@ TypeId Normalizer::typeFromNormal(const NormalizedType& norm)
return arena->addType(UnionType{std::move(result)});
}
bool isSubtype(TypeId subTy, TypeId superTy, NotNull<Scope> scope, NotNull<BuiltinTypes> builtinTypes, InternalErrorReporter& ice)
bool isSubtype(
TypeId subTy,
TypeId superTy,
NotNull<Scope> scope,
NotNull<BuiltinTypes> builtinTypes,
NotNull<Simplifier> simplifier,
InternalErrorReporter& ice
)
{
UnifierSharedState sharedState{&ice};
TypeArena arena;
@ -3478,7 +3539,7 @@ bool isSubtype(TypeId subTy, TypeId superTy, NotNull<Scope> scope, NotNull<Built
// Subtyping under DCR is not implemented using unification!
if (FFlag::LuauSolverV2)
{
Subtyping subtyping{builtinTypes, NotNull{&arena}, NotNull{&normalizer}, NotNull{&typeFunctionRuntime}, NotNull{&ice}};
Subtyping subtyping{builtinTypes, NotNull{&arena}, simplifier, NotNull{&normalizer}, NotNull{&typeFunctionRuntime}, NotNull{&ice}};
return subtyping.isSubtype(subTy, superTy, scope).isSubtype;
}
@ -3491,7 +3552,14 @@ bool isSubtype(TypeId subTy, TypeId superTy, NotNull<Scope> scope, NotNull<Built
}
}
bool isSubtype(TypePackId subPack, TypePackId superPack, NotNull<Scope> scope, NotNull<BuiltinTypes> builtinTypes, InternalErrorReporter& ice)
bool isSubtype(
TypePackId subPack,
TypePackId superPack,
NotNull<Scope> scope,
NotNull<BuiltinTypes> builtinTypes,
NotNull<Simplifier> simplifier,
InternalErrorReporter& ice
)
{
UnifierSharedState sharedState{&ice};
TypeArena arena;
@ -3504,7 +3572,7 @@ bool isSubtype(TypePackId subPack, TypePackId superPack, NotNull<Scope> scope, N
// Subtyping under DCR is not implemented using unification!
if (FFlag::LuauSolverV2)
{
Subtyping subtyping{builtinTypes, NotNull{&arena}, NotNull{&normalizer}, NotNull{&typeFunctionRuntime}, NotNull{&ice}};
Subtyping subtyping{builtinTypes, NotNull{&arena}, simplifier, NotNull{&normalizer}, NotNull{&typeFunctionRuntime}, NotNull{&ice}};
return subtyping.isSubtype(subPack, superPack, scope).isSubtype;
}

View file

@ -10,12 +10,15 @@
#include "Luau/TypeUtils.h"
#include "Luau/Unifier2.h"
LUAU_FASTFLAGVARIABLE(LuauArityMismatchOnUndersaturatedUnknownArguments)
namespace Luau
{
OverloadResolver::OverloadResolver(
NotNull<BuiltinTypes> builtinTypes,
NotNull<TypeArena> arena,
NotNull<Simplifier> simplifier,
NotNull<Normalizer> normalizer,
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
NotNull<Scope> scope,
@ -25,12 +28,13 @@ OverloadResolver::OverloadResolver(
)
: builtinTypes(builtinTypes)
, arena(arena)
, simplifier(simplifier)
, normalizer(normalizer)
, typeFunctionRuntime(typeFunctionRuntime)
, scope(scope)
, ice(reporter)
, limits(limits)
, subtyping({builtinTypes, arena, normalizer, typeFunctionRuntime, ice})
, subtyping({builtinTypes, arena, simplifier, normalizer, typeFunctionRuntime, ice})
, callLoc(callLocation)
{
}
@ -202,7 +206,7 @@ std::pair<OverloadResolver::Analysis, ErrorVec> OverloadResolver::checkOverload_
)
{
FunctionGraphReductionResult result = reduceTypeFunctions(
fnTy, callLoc, TypeFunctionContext{arena, builtinTypes, scope, normalizer, typeFunctionRuntime, ice, limits}, /*force=*/true
fnTy, callLoc, TypeFunctionContext{arena, builtinTypes, scope, simplifier, normalizer, typeFunctionRuntime, ice, limits}, /*force=*/true
);
if (!result.errors.empty())
return {OverloadIsNonviable, result.errors};
@ -252,15 +256,32 @@ std::pair<OverloadResolver::Analysis, ErrorVec> OverloadResolver::checkOverload_
}
// 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)
{
if (!subtyping.isSubtype(builtinTypes->nilType, requiredHead[i], scope).isSubtype)
if (FFlag::LuauArityMismatchOnUndersaturatedUnknownArguments)
{
auto [minParams, optMaxParams] = getParameterExtents(TxnLog::empty(), fn->argTypes);
TypeError error{fnExpr->location, CountMismatch{minParams, optMaxParams, args->head.size(), CountMismatch::Arg, isVariadic}};
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;
return {Analysis::ArityMismatch, {error}};
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)
{
auto [minParams, optMaxParams] = getParameterExtents(TxnLog::empty(), fn->argTypes);
TypeError error{fnExpr->location, CountMismatch{minParams, optMaxParams, args->head.size(), CountMismatch::Arg, isVariadic}};
return {Analysis::ArityMismatch, {error}};
}
}
}
@ -404,9 +425,10 @@ void OverloadResolver::add(Analysis analysis, TypeId ty, ErrorVec&& errors)
// we wrap calling the overload resolver in a separate function to reduce overall stack pressure in `solveFunctionCall`.
// this limits the lifetime of `OverloadResolver`, a large type, to only as long as it is actually needed.
std::optional<TypeId> selectOverload(
static std::optional<TypeId> selectOverload(
NotNull<BuiltinTypes> builtinTypes,
NotNull<TypeArena> arena,
NotNull<Simplifier> simplifier,
NotNull<Normalizer> normalizer,
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
NotNull<Scope> scope,
@ -417,8 +439,9 @@ std::optional<TypeId> selectOverload(
TypePackId argsPack
)
{
OverloadResolver resolver{builtinTypes, arena, normalizer, typeFunctionRuntime, scope, iceReporter, limits, location};
auto [status, overload] = resolver.selectOverload(fn, argsPack);
auto resolver =
std::make_unique<OverloadResolver>(builtinTypes, arena, simplifier, normalizer, typeFunctionRuntime, scope, iceReporter, limits, location);
auto [status, overload] = resolver->selectOverload(fn, argsPack);
if (status == OverloadResolver::Analysis::Ok)
return overload;
@ -432,6 +455,7 @@ std::optional<TypeId> selectOverload(
SolveResult solveFunctionCall(
NotNull<TypeArena> arena,
NotNull<BuiltinTypes> builtinTypes,
NotNull<Simplifier> simplifier,
NotNull<Normalizer> normalizer,
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
NotNull<InternalErrorReporter> iceReporter,
@ -443,22 +467,22 @@ SolveResult solveFunctionCall(
)
{
std::optional<TypeId> overloadToUse =
selectOverload(builtinTypes, arena, normalizer, typeFunctionRuntime, scope, iceReporter, limits, location, fn, argsPack);
selectOverload(builtinTypes, arena, simplifier, normalizer, typeFunctionRuntime, scope, iceReporter, limits, location, fn, argsPack);
if (!overloadToUse)
return {SolveResult::NoMatchingOverload};
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};
const bool occursCheckPassed = u2.unify(*overloadToUse, inferredTy);
if (!u2.genericSubstitutions.empty() || !u2.genericPackSubstitutions.empty())
{
Instantiation2 instantiation{arena, std::move(u2.genericSubstitutions), std::move(u2.genericPackSubstitutions)};
auto instantiation = std::make_unique<Instantiation2>(arena, std::move(u2.genericSubstitutions), std::move(u2.genericPackSubstitutions));
std::optional<TypePackId> subst = instantiation.substitute(resultPack);
std::optional<TypePackId> subst = instantiation->substitute(resultPack);
if (!subst)
return {SolveResult::CodeTooComplex};

View file

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

View file

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

View file

@ -4,6 +4,8 @@
#include "Luau/Ast.h"
#include "Luau/Module.h"
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
namespace Luau
{
@ -65,7 +67,13 @@ struct RequireTracer : AstVisitor
return true;
}
AstExpr* getDependent(AstExpr* node)
bool visit(AstTypePack* node) override
{
// allow resolving require inside `typeof` annotations
return FFlag::LuauStoreReturnTypesAsPackOnAst;
}
AstExpr* getDependent_DEPRECATED(AstExpr* node)
{
if (AstExprLocal* expr = node->as<AstExprLocal>())
return locals[expr->local];
@ -78,6 +86,27 @@ struct RequireTracer : AstVisitor
else
return nullptr;
}
AstNode* getDependent(AstNode* node)
{
if (AstExprLocal* expr = node->as<AstExprLocal>())
return locals[expr->local];
else if (AstExprIndexName* expr = node->as<AstExprIndexName>())
return expr->expr;
else if (AstExprIndexExpr* expr = node->as<AstExprIndexExpr>())
return expr->expr;
else if (AstExprCall* expr = node->as<AstExprCall>(); expr && expr->self)
return expr->func->as<AstExprIndexName>()->expr;
else if (AstExprGroup* expr = node->as<AstExprGroup>())
return expr->expr;
else if (AstExprTypeAssertion* expr = node->as<AstExprTypeAssertion>())
return expr->annotation;
else if (AstTypeGroup* expr = node->as<AstTypeGroup>())
return expr->type;
else if (AstTypeTypeof* expr = node->as<AstTypeTypeof>())
return expr->expr;
else
return nullptr;
}
void process()
{
@ -91,13 +120,15 @@ struct RequireTracer : AstVisitor
// push all dependent expressions to the work stack; note that the vector is modified during traversal
for (size_t i = 0; i < work.size(); ++i)
if (AstExpr* dep = getDependent(work[i]))
{
if (AstNode* dep = getDependent(work[i]))
work.push_back(dep);
}
// resolve all expressions to a module info
for (size_t i = work.size(); i > 0; --i)
{
AstExpr* expr = work[i - 1];
AstNode* expr = work[i - 1];
// when multiple expressions depend on the same one we push it to work queue multiple times
if (result.exprs.contains(expr))
@ -105,19 +136,22 @@ struct RequireTracer : AstVisitor
std::optional<ModuleInfo> info;
if (AstExpr* dep = getDependent(expr))
if (AstNode* dep = getDependent(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);
if (context && expr->is<AstExprLocal>())
info = *context; // locals just inherit their dependent context, no resolution required
else if (context && (expr->is<AstExprGroup>() || expr->is<AstTypeGroup>()))
info = *context; // simple group nodes propagate their value
else if (context && (expr->is<AstTypeTypeof>() || expr->is<AstExprTypeAssertion>()))
info = *context; // typeof type annotations will resolve to the typeof content
else if (AstExpr* asExpr = expr->asExpr())
info = fileResolver->resolveModule(context, asExpr);
}
else
else if (AstExpr* asExpr = expr->asExpr())
{
info = fileResolver->resolveModule(&moduleContext, expr);
info = fileResolver->resolveModule(&moduleContext, asExpr);
}
if (info)
@ -150,7 +184,7 @@ struct RequireTracer : AstVisitor
ModuleName currentModuleName;
DenseHashMap<AstLocal*, AstExpr*> locals;
std::vector<AstExpr*> work;
std::vector<AstNode*> work;
std::vector<AstExprCall*> requireCalls;
};

View file

@ -84,6 +84,17 @@ std::optional<TypeId> Scope::lookupUnrefinedType(DefId def) const
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
{
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;
}
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`.
void Scope::inheritAssignments(const ScopePtr& childScope)
{
@ -211,6 +245,16 @@ void Scope::inheritRefinements(const ScopePtr& childScope)
}
}
bool Scope::shouldWarnGlobal(std::string name) const
{
for (const Scope* current = this; current; current = current->parent.get())
{
if (current->globalsToWarn.contains(name))
return true;
}
return false;
}
bool subsumesStrict(Scope* left, Scope* right)
{
while (right)

View file

@ -6,6 +6,7 @@
#include "Luau/DenseHash.h"
#include "Luau/RecursionCounter.h"
#include "Luau/Set.h"
#include "Luau/Type.h"
#include "Luau/TypeArena.h"
#include "Luau/TypePairHash.h"
#include "Luau/TypeUtils.h"
@ -14,8 +15,10 @@
LUAU_FASTINT(LuauTypeReductionRecursionLimit)
LUAU_FASTFLAG(LuauSolverV2)
LUAU_DYNAMIC_FASTINTVARIABLE(LuauSimplificationComplexityLimit, 8);
LUAU_FASTFLAGVARIABLE(LuauFlagBasicIntersectFollows);
LUAU_DYNAMIC_FASTINTVARIABLE(LuauSimplificationComplexityLimit, 8)
LUAU_FASTFLAGVARIABLE(LuauSimplificationRecheckAssumption)
LUAU_FASTFLAGVARIABLE(LuauOptimizeFalsyAndTruthyIntersect)
LUAU_FASTFLAGVARIABLE(LuauSimplificationTableExternType)
namespace Luau
{
@ -31,25 +34,27 @@ struct TypeSimplifier
int recursionDepth = 0;
TypeId mkNegation(TypeId ty);
TypeId mkNegation(TypeId ty) const;
TypeId intersectFromParts(std::set<TypeId> parts);
TypeId intersectUnionWithType(TypeId unionTy, TypeId right);
TypeId intersectUnionWithType(TypeId left, TypeId right);
TypeId intersectUnions(TypeId left, TypeId right);
TypeId intersectNegatedUnion(TypeId unionTy, TypeId right);
TypeId intersectNegatedUnion(TypeId left, TypeId right);
TypeId intersectTypeWithNegation(TypeId a, TypeId b);
TypeId intersectNegations(TypeId a, TypeId b);
TypeId intersectTypeWithNegation(TypeId left, TypeId right);
TypeId intersectNegations(TypeId left, TypeId right);
TypeId intersectIntersectionWithType(TypeId left, TypeId right);
// Attempt to intersect the two types. Does not recurse. Does not handle
// unions, intersections, or negations.
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 ty, TypeId discriminant);
TypeId union_(TypeId ty, TypeId discriminant);
TypeId intersect(TypeId left, TypeId right);
TypeId union_(TypeId left, TypeId right);
TypeId simplify(TypeId ty);
TypeId simplify(TypeId ty, DenseHashSet<TypeId>& seen);
@ -313,12 +318,14 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
{
if (get<AnyType>(right))
return Relation::Subset;
else if (get<UnknownType>(right))
if (get<UnknownType>(right))
return Relation::Coincident;
else if (get<ErrorType>(right))
if (get<ErrorType>(right))
return Relation::Disjoint;
else
return Relation::Superset;
return Relation::Superset;
}
if (get<UnknownType>(right))
@ -328,8 +335,8 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
{
if (get<AnyType>(right))
return Relation::Coincident;
else
return Relation::Superset;
return Relation::Superset;
}
if (get<AnyType>(right))
@ -353,7 +360,7 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
// * FunctionType
// * TableType
// * MetatableType
// * ClassType
// * ExternType
// * UnionType
// * IntersectionType
// * NegationType
@ -361,26 +368,33 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
if (isTypeVariable(left) || isTypeVariable(right))
return Relation::Intersects;
if (FFlag::LuauSimplificationTableExternType)
{
// if either type is a type function, we cannot know if they'll be related.
if (get<TypeFunctionInstanceType>(left) || get<TypeFunctionInstanceType>(right))
return Relation::Intersects;
}
if (get<ErrorType>(left))
{
if (get<ErrorType>(right))
return Relation::Coincident;
else if (get<AnyType>(right))
return Relation::Subset;
else
return Relation::Disjoint;
return Relation::Disjoint;
}
if (get<ErrorType>(right))
else if (get<ErrorType>(right))
return flip(relate(right, left, seen));
if (get<NeverType>(left))
{
if (get<NeverType>(right))
return Relation::Coincident;
else
return Relation::Subset;
return Relation::Subset;
}
if (get<NeverType>(right))
else if (get<NeverType>(right))
return flip(relate(right, left, seen));
if (auto ut = get<IntersectionType>(left))
@ -444,52 +458,54 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
{
if (lp->type == rp->type)
return Relation::Coincident;
else
return Relation::Disjoint;
return Relation::Disjoint;
}
if (auto rs = get<SingletonType>(right))
{
if (lp->type == PrimitiveType::String && rs->variant.get_if<StringSingleton>())
return Relation::Superset;
else if (lp->type == PrimitiveType::Boolean && rs->variant.get_if<BooleanSingleton>())
if (lp->type == PrimitiveType::Boolean && rs->variant.get_if<BooleanSingleton>())
return Relation::Superset;
else
return Relation::Disjoint;
return Relation::Disjoint;
}
if (lp->type == PrimitiveType::Function)
{
if (get<FunctionType>(right))
return Relation::Superset;
else
return Relation::Disjoint;
return Relation::Disjoint;
}
if (lp->type == PrimitiveType::Table)
{
if (get<TableType>(right))
return Relation::Superset;
else
return Relation::Disjoint;
return Relation::Disjoint;
}
if (get<FunctionType>(right) || get<TableType>(right) || get<MetatableType>(right) || get<ClassType>(right))
if (get<FunctionType>(right) || get<TableType>(right) || get<MetatableType>(right) || get<ExternType>(right))
return Relation::Disjoint;
}
if (auto ls = get<SingletonType>(left))
{
if (get<FunctionType>(right) || get<TableType>(right) || get<MetatableType>(right) || get<ClassType>(right))
if (get<FunctionType>(right) || get<TableType>(right) || get<MetatableType>(right) || get<ExternType>(right))
return Relation::Disjoint;
if (get<PrimitiveType>(right))
return flip(relate(right, left, seen));
if (auto rs = get<SingletonType>(right))
{
if (ls->variant == rs->variant)
return Relation::Coincident;
else
return Relation::Disjoint;
return Relation::Disjoint;
}
}
@ -499,11 +515,11 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
{
if (rp->type == PrimitiveType::Function)
return Relation::Subset;
else
return Relation::Disjoint;
return Relation::Disjoint;
}
else
return Relation::Intersects;
return Relation::Intersects;
}
if (auto lt = get<TableType>(left))
@ -512,10 +528,11 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
{
if (rp->type == PrimitiveType::Table)
return Relation::Subset;
else
return Relation::Disjoint;
return Relation::Disjoint;
}
else if (auto rt = get<TableType>(right))
if (auto rt = get<TableType>(right))
{
// TODO PROBABLY indexers and metatables.
if (1 == rt->props.size())
@ -535,29 +552,58 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
*/
if (lt->props.size() > 1 && r == Relation::Superset)
return Relation::Intersects;
else
return r;
return r;
}
else if (1 == lt->props.size())
if (1 == lt->props.size())
return flip(relate(right, left, seen));
else
return Relation::Intersects;
return Relation::Intersects;
}
if (FFlag::LuauSimplificationTableExternType)
{
if (auto re = get<ExternType>(right))
{
Relation overall = Relation::Coincident;
for (auto& [name, prop] : lt->props)
{
if (auto propInExternType = re->props.find(name); propInExternType != re->props.end())
{
Relation propRel = relate(prop.type(), propInExternType->second.type());
if (propRel == Relation::Disjoint)
return Relation::Disjoint;
if (propRel == Relation::Coincident)
continue;
overall = Relation::Intersects;
}
}
return overall;
}
}
// TODO metatables
return Relation::Disjoint;
}
if (auto ct = get<ClassType>(left))
if (auto ct = get<ExternType>(left))
{
if (auto rct = get<ClassType>(right))
if (auto rct = get<ExternType>(right))
{
if (isSubclass(ct, rct))
return Relation::Subset;
else if (isSubclass(rct, ct))
if (isSubclass(rct, ct))
return Relation::Superset;
else
return Relation::Disjoint;
return Relation::Disjoint;
}
return Relation::Disjoint;
@ -573,7 +619,7 @@ Relation relate(TypeId left, TypeId right)
return relate(left, right, seen);
}
TypeId TypeSimplifier::mkNegation(TypeId ty)
TypeId TypeSimplifier::mkNegation(TypeId ty) const
{
TypeId result = nullptr;
@ -707,7 +753,9 @@ TypeId TypeSimplifier::intersectUnionWithType(TypeId left, TypeId right)
bool changed = false;
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}});
for (TypeId part : leftUnion)
@ -722,6 +770,13 @@ TypeId TypeSimplifier::intersectUnionWithType(TypeId left, TypeId right)
}
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)
@ -762,6 +817,13 @@ TypeId TypeSimplifier::intersectUnions(TypeId left, TypeId right)
continue;
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));
}
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)
{
const NegationType* leftNegation = get<NegationType>(left);
@ -1066,11 +1200,8 @@ TypeId TypeSimplifier::intersectIntersectionWithType(TypeId left, TypeId right)
std::optional<TypeId> TypeSimplifier::basicIntersect(TypeId left, TypeId right)
{
if (FFlag::LuauFlagBasicIntersectFollows)
{
left = follow(left);
right = follow(right);
}
left = follow(left);
right = follow(right);
if (get<AnyType>(left) && get<ErrorType>(right))
return right;
@ -1179,6 +1310,25 @@ std::optional<TypeId> TypeSimplifier::basicIntersect(TypeId left, TypeId right)
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);
if (left == right || Relation::Coincident == relation)
return left;
@ -1411,8 +1561,6 @@ TypeId TypeSimplifier::simplify(TypeId ty, DenseHashSet<TypeId>& seen)
SimplifyResult simplifyIntersection(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, TypeId left, TypeId right)
{
LUAU_ASSERT(FFlag::LuauSolverV2);
TypeSimplifier s{builtinTypes, arena};
// fprintf(stderr, "Intersect %s and %s ...\n", toString(left).c_str(), toString(right).c_str());
@ -1426,8 +1574,6 @@ SimplifyResult simplifyIntersection(NotNull<BuiltinTypes> builtinTypes, NotNull<
SimplifyResult simplifyIntersection(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, std::set<TypeId> parts)
{
LUAU_ASSERT(FFlag::LuauSolverV2);
TypeSimplifier s{builtinTypes, arena};
TypeId res = s.intersectFromParts(std::move(parts));
@ -1437,8 +1583,6 @@ SimplifyResult simplifyIntersection(NotNull<BuiltinTypes> builtinTypes, NotNull<
SimplifyResult simplifyUnion(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, TypeId left, TypeId right)
{
LUAU_ASSERT(FFlag::LuauSolverV2);
TypeSimplifier s{builtinTypes, arena};
TypeId res = s.union_(left, right);

View file

@ -2,22 +2,23 @@
#include "Luau/Substitution.h"
#include "Luau/Common.h"
#include "Luau/Clone.h"
#include "Luau/TxnLog.h"
#include "Luau/Type.h"
#include <algorithm>
#include <stdexcept>
LUAU_FASTINTVARIABLE(LuauTarjanChildLimit, 10000)
LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTINTVARIABLE(LuauTarjanPreallocationSize, 256);
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTINTVARIABLE(LuauTarjanPreallocationSize, 256)
LUAU_FASTFLAG(LuauSyntheticErrors)
LUAU_FASTFLAG(LuauDeprecatedAttribute)
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)>;
@ -57,8 +58,25 @@ static TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log, bool a
}
else if constexpr (std::is_same_v<T, ErrorType>)
{
LUAU_ASSERT(ty->persistent);
return ty;
if (FFlag::LuauSyntheticErrors)
{
LUAU_ASSERT(ty->persistent || a.synthetic);
if (ty->persistent)
return ty;
// While this code intentionally works (and clones) even if `a.synthetic` is `std::nullopt`,
// we still assert above because we consider it a bug to have a non-persistent error type
// without any associated metadata. We should always use the persistent version in such cases.
ErrorType clone = ErrorType{};
clone.synthetic = a.synthetic;
return dest.addType(clone);
}
else
{
LUAU_ASSERT(ty->persistent);
return ty;
}
}
else if constexpr (std::is_same_v<T, UnknownType>)
{
@ -76,15 +94,15 @@ static TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log, bool a
return dest.addType(a);
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.genericPacks = a.genericPacks;
clone.magicFunction = a.magicFunction;
clone.dcrMagicFunction = a.dcrMagicFunction;
clone.dcrMagicRefinement = a.dcrMagicRefinement;
clone.magic = a.magic;
clone.tags = a.tags;
clone.argNames = a.argNames;
clone.isCheckedFunction = a.isCheckedFunction;
if (FFlag::LuauDeprecatedAttribute)
clone.isDeprecatedFunction = a.isDeprecatedFunction;
return dest.addType(std::move(clone));
}
else if constexpr (std::is_same_v<T, TableType>)
@ -118,21 +136,16 @@ static TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log, bool a
clone.parts = a.parts;
return dest.addType(std::move(clone));
}
else if constexpr (std::is_same_v<T, ClassType>)
else if constexpr (std::is_same_v<T, ExternType>)
{
if (alwaysClone)
{
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));
}
else
return ty;
ExternType clone{a.name, a.props, a.parent, a.metatable, a.tags, a.userData, a.definitionModuleName, a.definitionLocation, a.indexer};
return dest.addType(std::move(clone));
}
else if constexpr (std::is_same_v<T, NegationType>)
return dest.addType(NegationType{a.ty});
else if constexpr (std::is_same_v<T, TypeFunctionInstanceType>)
{
TypeFunctionInstanceType clone{a.function, a.typeArguments, a.packArguments, a.userFuncName};
TypeFunctionInstanceType clone{a.function, a.typeArguments, a.packArguments, a.userFuncName, a.userFuncData};
return dest.addType(std::move(clone));
}
else
@ -239,21 +252,21 @@ void Tarjan::visitChildren(TypeId ty, int index)
for (TypePackId a : tfit->packArguments)
visitChild(a);
}
else if (const ClassType* ctv = get<ClassType>(ty))
else if (const ExternType* etv = get<ExternType>(ty))
{
for (const auto& [name, prop] : ctv->props)
for (const auto& [name, prop] : etv->props)
visitChild(prop.type());
if (ctv->parent)
visitChild(*ctv->parent);
if (etv->parent)
visitChild(*etv->parent);
if (ctv->metatable)
visitChild(*ctv->metatable);
if (etv->metatable)
visitChild(*etv->metatable);
if (ctv->indexer)
if (etv->indexer)
{
visitChild(ctv->indexer->indexType);
visitChild(ctv->indexer->indexResultType);
visitChild(etv->indexer->indexType);
visitChild(etv->indexer->indexResultType);
}
}
else if (const NegationType* ntv = get<NegationType>(ty))
@ -527,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)
{
return visitRoot(ty);
@ -537,6 +571,11 @@ TarjanResult Tarjan::findDirty(TypePackId tp)
return visitRoot(tp);
}
Substitution::Substitution(TypeArena* arena)
: Substitution(TxnLog::empty(), arena)
{
}
Substitution::Substitution(const TxnLog* log_, TypeArena* arena)
: arena(arena)
{
@ -637,7 +676,7 @@ void Substitution::resetState(const TxnLog* log, TypeArena* arena)
TypeId Substitution::clone(TypeId ty)
{
return shallowClone(ty, *arena, log, /* alwaysClone */ true);
return shallowClone(ty, *arena, log);
}
TypePackId Substitution::clone(TypePackId tp)
@ -799,21 +838,21 @@ void Substitution::replaceChildren(TypeId ty)
for (TypePackId& a : tfit->packArguments)
a = replace(a);
}
else if (ClassType* ctv = getMutable<ClassType>(ty))
else if (ExternType* etv = getMutable<ExternType>(ty))
{
for (auto& [name, prop] : ctv->props)
for (auto& [name, prop] : etv->props)
prop.setType(replace(prop.type()));
if (ctv->parent)
ctv->parent = replace(*ctv->parent);
if (etv->parent)
etv->parent = replace(*etv->parent);
if (ctv->metatable)
ctv->metatable = replace(*ctv->metatable);
if (etv->metatable)
etv->metatable = replace(*etv->metatable);
if (ctv->indexer)
if (etv->indexer)
{
ctv->indexer->indexType = replace(ctv->indexer->indexType);
ctv->indexer->indexResultType = replace(ctv->indexer->indexResultType);
etv->indexer->indexType = replace(etv->indexer->indexType);
etv->indexer->indexResultType = replace(etv->indexer->indexResultType);
}
}
else if (NegationType* ntv = getMutable<NegationType>(ty))
@ -853,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

View file

@ -7,13 +7,11 @@
#include "Luau/Normalize.h"
#include "Luau/RecursionCounter.h"
#include "Luau/Scope.h"
#include "Luau/StringUtils.h"
#include "Luau/Substitution.h"
#include "Luau/ToString.h"
#include "Luau/TxnLog.h"
#include "Luau/Type.h"
#include "Luau/TypeArena.h"
#include "Luau/TypeCheckLimits.h"
#include "Luau/TypeFunction.h"
#include "Luau/TypePack.h"
#include "Luau/TypePath.h"
@ -22,8 +20,8 @@
#include <algorithm>
LUAU_FASTFLAGVARIABLE(DebugLuauSubtypingCheckPathValidity)
LUAU_DYNAMIC_FASTINT(LuauTypeSolverRelease)
LUAU_FASTFLAGVARIABLE(LuauRetrySubtypingWithoutHiddenPack)
LUAU_FASTINTVARIABLE(LuauSubtypingReasoningLimit, 100)
LUAU_FASTFLAGVARIABLE(LuauSubtypingEnableReasoningLimit)
namespace Luau
{
@ -33,7 +31,7 @@ struct VarianceFlipper
Subtyping::Variance* variance;
Subtyping::Variance oldValue;
VarianceFlipper(Subtyping::Variance* v)
explicit VarianceFlipper(Subtyping::Variance* v)
: variance(v)
, oldValue(*v)
{
@ -101,6 +99,9 @@ static SubtypingReasonings mergeReasonings(const SubtypingReasonings& a, const S
else
result.insert(r);
}
if (FFlag::LuauSubtypingEnableReasoningLimit && result.size() >= size_t(FInt::LuauSubtypingReasoningLimit))
return result;
}
for (const SubtypingReasoning& r : b)
@ -117,6 +118,9 @@ static SubtypingReasonings mergeReasonings(const SubtypingReasonings& a, const S
else
result.insert(r);
}
if (FFlag::LuauSubtypingEnableReasoningLimit && result.size() >= size_t(FInt::LuauSubtypingReasoningLimit))
return result;
}
return result;
@ -309,7 +313,7 @@ struct ApplyMappedGenerics : Substitution
bool ignoreChildren(TypeId ty) override
{
if (get<ClassType>(ty))
if (get<ExternType>(ty))
return true;
return ty->persistent;
@ -397,12 +401,14 @@ TypePackId* SubtypingEnvironment::getMappedPackBounds(TypePackId tp)
Subtyping::Subtyping(
NotNull<BuiltinTypes> builtinTypes,
NotNull<TypeArena> typeArena,
NotNull<Simplifier> simplifier,
NotNull<Normalizer> normalizer,
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
NotNull<InternalErrorReporter> iceReporter
)
: builtinTypes(builtinTypes)
, arena(typeArena)
, simplifier(simplifier)
, normalizer(normalizer)
, typeFunctionRuntime(typeFunctionRuntime)
, iceReporter(iceReporter)
@ -415,6 +421,14 @@ SubtypingResult Subtyping::isSubtype(TypeId subTy, TypeId superTy, NotNull<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)
{
const auto& lb = bounds.lowerBound;
@ -592,7 +606,12 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
if (!result.isSubtype && !result.normalizationTooComplex)
{
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();
result = semantic;
@ -607,7 +626,12 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
if (!result.isSubtype && !result.normalizationTooComplex)
{
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
// potentially contain invalid paths.
@ -718,9 +742,9 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
result = isCovariantWith(env, p, scope);
else if (auto p = get2<MetatableType, TableType>(subTy, superTy))
result = isCovariantWith(env, p, scope);
else if (auto p = get2<ClassType, ClassType>(subTy, superTy))
else if (auto p = get2<ExternType, ExternType>(subTy, superTy))
result = isCovariantWith(env, p, scope);
else if (auto p = get2<ClassType, TableType>(subTy, superTy))
else if (auto p = get2<ExternType, TableType>(subTy, superTy))
result = isCovariantWith(env, subTy, p.first, superTy, p.second, scope);
else if (auto p = get2<TableType, PrimitiveType>(subTy, superTy))
result = isCovariantWith(env, p, scope);
@ -753,7 +777,8 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
// Match head types pairwise
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
@ -766,7 +791,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
for (size_t i = headSize; i < superHead.size(); ++i)
results.push_back(isCovariantWith(env, vt->ty, superHead[i], scope)
.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))
{
@ -820,7 +845,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
{
for (size_t i = headSize; i < subHead.size(); ++i)
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()));
}
else if (auto gt = get<GenericTypePack>(*superTail))
@ -858,7 +883,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
else
return SubtypingResult{false}
.withSuperComponent(TypePath::PackField::Tail)
.withError({scope->location, UnexpectedTypePackInSubtyping{*subTail}});
.withError({scope->location, UnexpectedTypePackInSubtyping{*superTail}});
}
else
return {false};
@ -1081,6 +1106,10 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
for (TypeId ty : superUnion)
{
SubtypingResult next = isCovariantWith(env, subTy, ty, scope);
if (next.normalizationTooComplex)
return SubtypingResult{false, /* normalizationTooComplex */ true};
if (next.isSubtype)
return SubtypingResult{true};
}
@ -1099,7 +1128,13 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Unio
std::vector<SubtypingResult> subtypings;
size_t i = 0;
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);
}
@ -1109,7 +1144,13 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
std::vector<SubtypingResult> subtypings;
size_t i = 0;
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);
}
@ -1119,7 +1160,13 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Inte
std::vector<SubtypingResult> subtypings;
size_t i = 0;
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);
}
@ -1287,7 +1334,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Type
}
// the top class type is not actually a primitive type, so the negation of
// any one of them includes the top class type.
else if (auto p = get2<ClassType, PrimitiveType>(subTy, negatedTy))
else if (auto p = get2<ExternType, PrimitiveType>(subTy, negatedTy))
result = {true};
else if (auto p = get<PrimitiveType>(negatedTy); p && is<TableType, MetatableType>(subTy))
result = {p->type != PrimitiveType::Table};
@ -1295,9 +1342,9 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Type
result = {p.second->type != PrimitiveType::Function};
else if (auto p = get2<SingletonType, SingletonType>(subTy, negatedTy))
result = {*p.first != *p.second};
else if (auto p = get2<ClassType, ClassType>(subTy, negatedTy))
else if (auto p = get2<ExternType, ExternType>(subTy, negatedTy))
result = SubtypingResult::negate(isCovariantWith(env, p.first, p.second, scope));
else if (get2<FunctionType, ClassType>(subTy, negatedTy))
else if (get2<FunctionType, ExternType>(subTy, negatedTy))
result = {true};
else if (is<ErrorType, FunctionType, TableType, MetatableType>(negatedTy))
iceReporter->ice("attempting to negate a non-testable type");
@ -1395,17 +1442,9 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Tabl
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const MetatableType* subMt, const MetatableType* superMt, NotNull<Scope> scope)
{
if (DFInt::LuauTypeSolverRelease >= 646)
{
return isCovariantWith(env, subMt->table, superMt->table, scope)
.withBothComponent(TypePath::TypeField::Table)
.andAlso(isCovariantWith(env, subMt->metatable, superMt->metatable, scope).withBothComponent(TypePath::TypeField::Metatable));
}
else
{
return isCovariantWith(env, subMt->table, superMt->table, scope)
.andAlso(isCovariantWith(env, subMt->metatable, superMt->metatable, scope).withBothComponent(TypePath::TypeField::Metatable));
}
return isCovariantWith(env, subMt->table, superMt->table, scope)
.withBothComponent(TypePath::TypeField::Table)
.andAlso(isCovariantWith(env, subMt->metatable, superMt->metatable, scope).withBothComponent(TypePath::TypeField::Metatable));
}
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const MetatableType* subMt, const TableType* superTable, NotNull<Scope> scope)
@ -1417,7 +1456,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Meta
// of the supertype table.
//
// 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
// compatible properties/shapes. We'll revisit this later when we have a
// better understanding of how important this is.
@ -1430,15 +1469,15 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Meta
}
}
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const ClassType* subClass, const ClassType* superClass, NotNull<Scope> scope)
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const ExternType* subExternType, const ExternType* superExternType, NotNull<Scope> scope)
{
return {isSubclass(subClass, superClass)};
return {isSubclass(subExternType, superExternType)};
}
SubtypingResult Subtyping::isCovariantWith(
SubtypingEnvironment& env,
TypeId subTy,
const ClassType* subClass,
const ExternType* subExternType,
TypeId superTy,
const TableType* superTable,
NotNull<Scope> scope
@ -1450,7 +1489,7 @@ SubtypingResult Subtyping::isCovariantWith(
for (const auto& [name, prop] : superTable->props)
{
if (auto classProp = lookupClassProp(subClass, name))
if (auto classProp = lookupExternTypeProp(subExternType, name))
{
result.andAlso(isCovariantWith(env, *classProp, prop, name, scope));
}
@ -1481,15 +1520,14 @@ SubtypingResult Subtyping::isCovariantWith(
// If subtyping failed in the argument packs, we should check if there's a hidden variadic tail and try ignoring it.
// This might cause subtyping correctly because the sub type here may not have a hidden variadic tail or equivalent.
if (FFlag::LuauRetrySubtypingWithoutHiddenPack && !result.isSubtype)
if (!result.isSubtype)
{
auto [arguments, tail] = flatten(superFunction->argTypes);
if (auto variadic = get<VariadicTypePack>(tail); variadic && variadic->hidden)
{
result.orElse(
isContravariantWith(env, subFunction->argTypes, arena->addTypePack(TypePack{arguments}), scope).withBothComponent(TypePath::PackField::Arguments)
);
result.orElse(isContravariantWith(env, subFunction->argTypes, arena->addTypePack(TypePack{arguments}), scope)
.withBothComponent(TypePath::PackField::Arguments));
}
}
}
@ -1621,7 +1659,7 @@ SubtypingResult Subtyping::isCovariantWith(
SubtypingResult result = isCovariantWith(env, subNorm->tops, superNorm->tops, scope);
result.andAlso(isCovariantWith(env, subNorm->booleans, superNorm->booleans, scope));
result.andAlso(
isCovariantWith(env, subNorm->classes, superNorm->classes, scope).orElse(isCovariantWith(env, subNorm->classes, superNorm->tables, scope))
isCovariantWith(env, subNorm->externTypes, superNorm->externTypes, scope).orElse(isCovariantWith(env, subNorm->externTypes, superNorm->tables, scope))
);
result.andAlso(isCovariantWith(env, subNorm->errors, superNorm->errors, scope));
result.andAlso(isCovariantWith(env, subNorm->nils, superNorm->nils, scope));
@ -1638,24 +1676,24 @@ SubtypingResult Subtyping::isCovariantWith(
SubtypingResult Subtyping::isCovariantWith(
SubtypingEnvironment& env,
const NormalizedClassType& subClass,
const NormalizedClassType& superClass,
const NormalizedExternType& subExternType,
const NormalizedExternType& superExternType,
NotNull<Scope> scope
)
{
for (const auto& [subClassTy, _] : subClass.classes)
for (const auto& [subExternTypeTy, _] : subExternType.externTypes)
{
SubtypingResult result;
for (const auto& [superClassTy, superNegations] : superClass.classes)
for (const auto& [superExternTypeTy, superNegations] : superExternType.externTypes)
{
result.orElse(isCovariantWith(env, subClassTy, superClassTy, scope));
result.orElse(isCovariantWith(env, subExternTypeTy, superExternTypeTy, scope));
if (!result.isSubtype)
continue;
for (TypeId negation : superNegations)
{
result.andAlso(SubtypingResult::negate(isCovariantWith(env, subClassTy, negation, scope)));
result.andAlso(SubtypingResult::negate(isCovariantWith(env, subExternTypeTy, negation, scope)));
if (result.isSubtype)
break;
}
@ -1670,17 +1708,17 @@ SubtypingResult Subtyping::isCovariantWith(
SubtypingResult Subtyping::isCovariantWith(
SubtypingEnvironment& env,
const NormalizedClassType& subClass,
const NormalizedExternType& subExternType,
const TypeIds& superTables,
NotNull<Scope> scope
)
{
for (const auto& [subClassTy, _] : subClass.classes)
for (const auto& [subExternTypeTy, _] : subExternType.externTypes)
{
SubtypingResult result;
for (TypeId superTableTy : superTables)
result.orElse(isCovariantWith(env, subClassTy, superTableTy, scope));
result.orElse(isCovariantWith(env, subExternTypeTy, superTableTy, scope));
if (!result.isSubtype)
return result;
@ -1768,7 +1806,12 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Type
{
results.emplace_back();
for (TypeId superTy : superTypes)
{
results.back().orElse(isCovariantWith(env, subTy, superTy, scope));
if (results.back().normalizationTooComplex)
return SubtypingResult{false, /* normalizationTooComplex */ true};
}
}
return SubtypingResult::all(results);
@ -1870,7 +1913,7 @@ TypeId Subtyping::makeAggregateType(const Container& container, TypeId orElse)
std::pair<TypeId, ErrorVec> Subtyping::handleTypeFunctionReductionResult(const TypeFunctionInstanceType* functionInstance, NotNull<Scope> scope)
{
TypeFunctionContext context{arena, builtinTypes, scope, normalizer, typeFunctionRuntime, iceReporter, NotNull{&limits}};
TypeFunctionContext context{arena, builtinTypes, scope, simplifier, normalizer, typeFunctionRuntime, iceReporter, NotNull{&limits}};
TypeId function = arena->addType(*functionInstance);
FunctionGraphReductionResult result = reduceTypeFunctions(function, {}, context, true);
ErrorVec errors;

View file

@ -14,10 +14,8 @@ bool Symbol::operator==(const Symbol& rhs) const
return local == rhs.local;
else if (global.value)
return rhs.global.value && global == rhs.global.value; // Subtlety: AstName::operator==(const char*) uses strcmp, not pointer identity.
else if (FFlag::LuauSolverV2)
return !rhs.local && !rhs.global.value; // Reflexivity: we already know `this` Symbol is empty, so check that rhs is.
else
return false;
return !rhs.local && !rhs.global.value; // Reflexivity: we already know `this` Symbol is empty, so check that rhs is.
}
std::string toString(const Symbol& name)

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