mirror of
https://github.com/luau-lang/luau.git
synced 2025-05-04 10:33:46 +01:00
Compare commits
280 commits
Author | SHA1 | Date | |
---|---|---|---|
|
72f6c8b679 | ||
|
d9aa88e772 | ||
|
c51743268b | ||
|
a2303a6ae6 | ||
|
d110c812bb | ||
|
b6457801c7 | ||
|
50f32a1400 | ||
|
a8d14596e7 | ||
|
cbe078b3b4 | ||
|
ee1c6bf0db | ||
|
6b33251b89 | ||
|
12dac2f1f4 | ||
|
2621488abe | ||
|
5f42e63a73 | ||
|
e0b55a9cb1 | ||
|
b0c3f40b0c | ||
|
de9f5d6eb6 | ||
|
640ebbc0a5 | ||
|
6a21dba682 | ||
|
c1e2f650db | ||
|
c2e72666d9 | ||
|
86bf4ae42d | ||
|
29a5198055 | ||
|
14ccae9b44 | ||
|
9c198413ec | ||
|
bd4fe54f4b | ||
|
77642988c2 | ||
|
2e61028cba | ||
|
f8a1e0129d | ||
|
c13b5b7440 | ||
|
6061a14e9f | ||
|
82c9383a39 | ||
|
29047504da | ||
|
67e9d85124 | ||
|
24cacc94ed | ||
|
c759cd5581 | ||
|
945c510b3c | ||
|
8a4ef26f89 | ||
|
9a102e2aff | ||
|
8f94786ceb | ||
|
7ab3482003 | ||
|
2e6fdd90a0 | ||
|
87eac7befa | ||
|
79cdfe1094 | ||
|
906a00d498 | ||
|
8b8118b027 | ||
|
d0222bb554 | ||
|
dfdcff0897 | ||
|
341aa38768 | ||
|
230ab81326 | ||
|
8cc289fae4 | ||
|
8f2ab4cbad | ||
|
b5801d3377 | ||
|
d19a5f0699 | ||
|
4fa6e97caa | ||
|
0bd9321957 | ||
|
dd7a19d8ea | ||
|
7a6142e792 | ||
|
b1b21f395a | ||
|
c2e4ee0203 | ||
|
e905e30570 | ||
|
4709fec28c | ||
|
ebd074803f | ||
|
3c87474398 | ||
|
f6f4d92107 | ||
|
d1025d0029 | ||
|
53e6e4b8f0 | ||
|
e6bf71871a | ||
|
9dc829b584 | ||
|
df67e4d62c | ||
|
af9d9ba13e | ||
|
4399b17f95 | ||
|
c799a548e4 | ||
|
c6cf1f829a | ||
|
25de210308 | ||
|
ee5b473b86 | ||
|
e85fb91cfd | ||
|
ed05da573a | ||
|
1de169f006 | ||
|
ce9f1eb905 | ||
|
bbd18bf0bd | ||
|
47e3123863 | ||
|
fb8c190849 | ||
|
268e0b2ab7 | ||
|
837bba31e4 | ||
|
5d74685798 | ||
|
a7324ce8fb | ||
|
03c9fa721e | ||
|
b5d3544d18 | ||
|
0276d18314 | ||
|
b1fae55636 | ||
|
50e8dd0d71 | ||
|
dafee8bbef | ||
|
c9ff5d1c0a | ||
|
848fa0be8e | ||
|
b76941b678 | ||
|
507832b1f0 | ||
|
68b9214b03 | ||
|
a197f044b5 | ||
|
ef95ce5266 | ||
|
39899ade4f | ||
|
34801b9310 | ||
|
70d5a0ede8 | ||
|
74c1ac33a9 | ||
|
ee6a45b13b | ||
|
69776f6fc1 | ||
|
f52fe9f351 | ||
|
a02bee5acc | ||
|
5bb24aa888 | ||
|
38d76e658d | ||
|
0e1504fa52 | ||
|
afa4fa601e | ||
|
912798e5c7 | ||
|
40a054abe8 | ||
|
aa2e5c096d | ||
|
aabaf01484 | ||
|
469ba7712f | ||
|
8531df04c9 | ||
|
d7bbe3ff04 | ||
|
a3da985449 | ||
|
e8880490d2 | ||
|
7a7521a7ab | ||
|
45594a571d | ||
|
48f3c85cba | ||
|
0106dfaffa | ||
|
9655780bd3 | ||
|
66ba3608ca | ||
|
0d6b70b80b | ||
|
d66e088af8 | ||
|
a45eb2c9e0 | ||
|
4df4a1d408 | ||
|
23e9fa53cd | ||
|
cd27a20223 | ||
|
ef8e1e3945 | ||
|
538b0c071f | ||
|
15557d8d01 | ||
|
f86f0a48ff | ||
|
78ae885a51 | ||
|
a74031bae7 | ||
|
25db8ff2cb | ||
|
5b9344a480 | ||
|
d414b56acb | ||
|
caeba5a742 | ||
|
ebdbcb1942 | ||
|
3613ee9ddc | ||
|
8c4f9070c5 | ||
|
497c3edb91 | ||
|
c229a9e598 | ||
|
057bdf3556 | ||
|
8a99f25381 | ||
|
1adbd45b20 | ||
|
5b401524f5 | ||
|
fad8aaf0ab | ||
|
28c48cf460 | ||
|
bb7d5cf6c8 | ||
|
48c0e9f625 | ||
|
fab4eec97b | ||
|
7dd10b16dc | ||
|
ac97a491c1 | ||
|
79966e94a7 | ||
|
6fd26c55ff | ||
|
5a23350108 | ||
|
a62f6619c2 | ||
|
1c1476fa2d | ||
|
bc1bead287 | ||
|
2010be1575 | ||
|
cfcb545b39 | ||
|
da48758d04 | ||
|
c5a2a4b68f | ||
|
240a9d8f7f | ||
|
dafb44dd3d | ||
|
5d2e3de4b0 | ||
|
816cb1d1c4 | ||
|
1ba3e5fd7b | ||
|
2a1359dad0 | ||
|
91790efb8d | ||
|
58b98097c5 | ||
|
40e03164f7 | ||
|
eae092a45a | ||
|
5dd97352c1 | ||
|
93a89dcfa1 | ||
|
09e46d1980 | ||
|
fede4d6393 | ||
|
241fcf8eba | ||
|
26fb155507 | ||
|
bad9e1476e | ||
|
0f61e4e7a4 | ||
|
ca46dd6fe8 | ||
|
e5de2ed3cc | ||
|
0386eec734 | ||
|
f172471b87 | ||
|
f76a99b800 | ||
|
9bce20cb5c | ||
|
1ad7b9cc56 | ||
|
f4ecf437ba | ||
|
93468ca88d | ||
|
88dd289100 | ||
|
76ed1a5370 | ||
|
8d0a650a25 | ||
|
50a2f8daa8 | ||
|
641e9f6eb5 | ||
|
5cf508a73b | ||
|
67b9145268 | ||
|
858b93a5f3 | ||
|
f97e96dc29 | ||
|
7c346a0a69 | ||
|
0f0c0e4d28 | ||
|
5aa6d99340 | ||
|
0f1973954c | ||
|
9cb93a98bc | ||
|
2e1c0404d1 | ||
|
6bef0b10ca | ||
|
c730a51ca8 | ||
|
3e1b4130ea | ||
|
77598ed0a6 | ||
|
fb90dc083b | ||
|
d8f49d6ca2 | ||
|
4931165635 | ||
|
a30b2aebfd | ||
|
6fff08b621 | ||
|
c1bbf1ebec | ||
|
5e9a567e09 | ||
|
f27d4f52c3 | ||
|
1ebdfe093a | ||
|
9e1a26c9c8 | ||
|
27a05c0023 | ||
|
ed4ce84e55 | ||
|
f36cae2109 | ||
|
532fd109e7 | ||
|
d4a266528a | ||
|
0ab33af5c2 | ||
|
ccb5385a72 | ||
|
22686ef1b0 | ||
|
b5f2813ab4 | ||
|
1778950554 | ||
|
158d60c223 | ||
|
ffd9f32d2c | ||
|
45e72ee97b | ||
|
1a6da94547 | ||
|
5559c7fbd5 | ||
|
88d2b93351 | ||
|
f8f0dd94f7 | ||
|
dfa512ba36 | ||
|
ce2665d9f1 | ||
|
e3aba9292e | ||
|
0edacdded4 | ||
|
2fd3da3c14 | ||
|
59a29fd322 | ||
|
064d845269 | ||
|
38aa074767 | ||
|
2f7509dedd | ||
|
50b4779798 | ||
|
79328350ab | ||
|
74b0d004ec | ||
|
0d5c842434 | ||
|
d4883bfb32 | ||
|
adea0f883c | ||
|
d6226187b0 | ||
|
89090a16a6 | ||
|
f9c5cdd4fb | ||
|
139b169361 | ||
|
6068432285 | ||
|
f5441d7030 | ||
|
69728e87cf | ||
|
c48ffc32a4 | ||
|
c932b8e03f | ||
|
41669c9f30 | ||
|
c592f50e20 | ||
|
79854143e4 | ||
|
e70eec0c59 | ||
|
98d2db7325 | ||
|
557e77a676 | ||
|
2d4a544709 | ||
|
43201531b3 | ||
|
674c6c40c0 | ||
|
e57cbf6132 | ||
|
63436480a0 | ||
|
4b68791b2c | ||
|
efe133abe9 | ||
|
a0b9950541 |
508 changed files with 37729 additions and 11496 deletions
16
.github/workflows/build.yml
vendored
16
.github/workflows/build.yml
vendored
|
@ -46,9 +46,9 @@ jobs:
|
||||||
- name: make cli
|
- name: make cli
|
||||||
run: |
|
run: |
|
||||||
make -j2 config=sanitize werror=1 luau luau-analyze luau-compile # match config with tests to improve build time
|
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 tests/conformance/assert.luau
|
||||||
./luau-analyze tests/conformance/assert.lua
|
./luau-analyze tests/conformance/assert.luau
|
||||||
./luau-compile tests/conformance/assert.lua
|
./luau-compile tests/conformance/assert.luau
|
||||||
|
|
||||||
windows:
|
windows:
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
|
@ -81,12 +81,12 @@ jobs:
|
||||||
shell: bash # necessary for fail-fast
|
shell: bash # necessary for fail-fast
|
||||||
run: |
|
run: |
|
||||||
cmake --build . --target Luau.Repl.CLI Luau.Analyze.CLI Luau.Compile.CLI --config Debug # match config with tests to improve build time
|
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 tests/conformance/assert.luau
|
||||||
Debug/luau-analyze tests/conformance/assert.lua
|
Debug/luau-analyze tests/conformance/assert.luau
|
||||||
Debug/luau-compile tests/conformance/assert.lua
|
Debug/luau-compile tests/conformance/assert.luau
|
||||||
|
|
||||||
coverage:
|
coverage:
|
||||||
runs-on: ubuntu-20.04 # needed for clang++-10 to avoid gcov compatibility issues
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: install
|
- name: install
|
||||||
|
@ -94,7 +94,7 @@ jobs:
|
||||||
sudo apt install llvm
|
sudo apt install llvm
|
||||||
- name: make coverage
|
- name: make coverage
|
||||||
run: |
|
run: |
|
||||||
CXX=clang++-10 make -j2 config=coverage native=1 coverage
|
CXX=clang++ make -j2 config=coverage native=1 coverage
|
||||||
- name: upload coverage
|
- name: upload coverage
|
||||||
uses: codecov/codecov-action@v3
|
uses: codecov/codecov-action@v3
|
||||||
with:
|
with:
|
||||||
|
|
4
.github/workflows/new-release.yml
vendored
4
.github/workflows/new-release.yml
vendored
|
@ -29,8 +29,8 @@ jobs:
|
||||||
build:
|
build:
|
||||||
needs: ["create-release"]
|
needs: ["create-release"]
|
||||||
strategy:
|
strategy:
|
||||||
matrix: # using ubuntu-20.04 to build a Linux binary targeting older glibc to improve compatibility
|
matrix: # not using ubuntu-latest to improve compatibility
|
||||||
os: [{name: ubuntu, version: ubuntu-20.04}, {name: macos, version: macos-latest}, {name: windows, version: windows-latest}]
|
os: [{name: ubuntu, version: ubuntu-22.04}, {name: macos, version: macos-latest}, {name: windows, version: windows-latest}]
|
||||||
name: ${{matrix.os.name}}
|
name: ${{matrix.os.name}}
|
||||||
runs-on: ${{matrix.os.version}}
|
runs-on: ${{matrix.os.version}}
|
||||||
steps:
|
steps:
|
||||||
|
|
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
|
@ -13,8 +13,8 @@ on:
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
strategy:
|
strategy:
|
||||||
matrix: # using ubuntu-20.04 to build a Linux binary targeting older glibc to improve compatibility
|
matrix: # not using ubuntu-latest to improve compatibility
|
||||||
os: [{name: ubuntu, version: ubuntu-20.04}, {name: macos, version: macos-latest}, {name: windows, version: windows-latest}]
|
os: [{name: ubuntu, version: ubuntu-22.04}, {name: macos, version: macos-latest}, {name: windows, version: windows-latest}]
|
||||||
name: ${{matrix.os.name}}
|
name: ${{matrix.os.name}}
|
||||||
runs-on: ${{matrix.os.version}}
|
runs-on: ${{matrix.os.version}}
|
||||||
steps:
|
steps:
|
||||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -13,6 +13,7 @@
|
||||||
/luau
|
/luau
|
||||||
/luau-tests
|
/luau-tests
|
||||||
/luau-analyze
|
/luau-analyze
|
||||||
|
/luau-bytecode
|
||||||
/luau-compile
|
/luau-compile
|
||||||
__pycache__
|
__pycache__
|
||||||
.cache
|
.cache
|
||||||
|
|
|
@ -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
|
|
|
@ -57,7 +57,7 @@ struct AutocompleteEntry
|
||||||
// Set if this suggestion matches the type expected in the context
|
// Set if this suggestion matches the type expected in the context
|
||||||
TypeCorrectKind typeCorrect = TypeCorrectKind::None;
|
TypeCorrectKind typeCorrect = TypeCorrectKind::None;
|
||||||
|
|
||||||
std::optional<const ClassType*> containingClass = std::nullopt;
|
std::optional<const ExternType*> containingExternType = std::nullopt;
|
||||||
std::optional<const Property*> prop = std::nullopt;
|
std::optional<const Property*> prop = std::nullopt;
|
||||||
std::optional<std::string> documentationSymbol = std::nullopt;
|
std::optional<std::string> documentationSymbol = std::nullopt;
|
||||||
Tags tags;
|
Tags tags;
|
||||||
|
@ -85,7 +85,7 @@ struct AutocompleteResult
|
||||||
};
|
};
|
||||||
|
|
||||||
using StringCompletionCallback =
|
using StringCompletionCallback =
|
||||||
std::function<std::optional<AutocompleteEntryMap>(std::string tag, std::optional<const ClassType*> ctx, std::optional<std::string> contents)>;
|
std::function<std::optional<AutocompleteEntryMap>(std::string tag, std::optional<const ExternType*> ctx, std::optional<std::string> contents)>;
|
||||||
|
|
||||||
constexpr char kGeneratedAnonymousFunctionEntryName[] = "function (anonymous autofilled)";
|
constexpr char kGeneratedAnonymousFunctionEntryName[] = "function (anonymous autofilled)";
|
||||||
|
|
||||||
|
|
|
@ -65,14 +65,12 @@ TypeId makeFunction( // Polymorphic
|
||||||
bool checked = false
|
bool checked = false
|
||||||
);
|
);
|
||||||
|
|
||||||
void attachMagicFunction(TypeId ty, MagicFunction fn);
|
void attachMagicFunction(TypeId ty, std::shared_ptr<MagicFunction> fn);
|
||||||
void attachDcrMagicFunction(TypeId ty, DcrMagicFunction fn);
|
|
||||||
void attachDcrMagicRefinement(TypeId ty, DcrMagicRefinement fn);
|
|
||||||
void attachDcrMagicFunctionTypeCheck(TypeId ty, DcrMagicFunctionTypeCheck fn);
|
|
||||||
Property makeProperty(TypeId ty, std::optional<std::string> documentationSymbol = std::nullopt);
|
Property makeProperty(TypeId ty, std::optional<std::string> documentationSymbol = std::nullopt);
|
||||||
void assignPropDocumentationSymbols(TableType::Props& props, const std::string& baseName);
|
void assignPropDocumentationSymbols(TableType::Props& props, const std::string& baseName);
|
||||||
|
|
||||||
std::string getBuiltinDefinitionSource();
|
std::string getBuiltinDefinitionSource();
|
||||||
|
std::string getTypeFunctionDefinitionSource();
|
||||||
|
|
||||||
void addGlobalBinding(GlobalTypes& globals, const std::string& name, TypeId ty, const std::string& packageName);
|
void addGlobalBinding(GlobalTypes& globals, const std::string& name, TypeId ty, const std::string& packageName);
|
||||||
void addGlobalBinding(GlobalTypes& globals, const std::string& name, Binding binding);
|
void addGlobalBinding(GlobalTypes& globals, const std::string& name, Binding binding);
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
#include <Luau/NotNull.h>
|
#include <Luau/NotNull.h>
|
||||||
#include "Luau/TypeArena.h"
|
#include "Luau/TypeArena.h"
|
||||||
#include "Luau/Type.h"
|
#include "Luau/Type.h"
|
||||||
|
#include "Luau/Scope.h"
|
||||||
|
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
|
@ -26,13 +27,22 @@ struct CloneState
|
||||||
* while `clone` will make a deep copy of the entire type and its every component.
|
* while `clone` will make a deep copy of the entire type and its every component.
|
||||||
*
|
*
|
||||||
* Be mindful about which behavior you actually _want_.
|
* 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);
|
TypePackId shallowClone(TypePackId tp, TypeArena& dest, CloneState& cloneState, bool ignorePersistent = false);
|
||||||
TypeId shallowClone(TypeId typeId, TypeArena& dest, CloneState& cloneState);
|
TypeId shallowClone(TypeId typeId, TypeArena& dest, CloneState& cloneState, bool ignorePersistent = false);
|
||||||
|
|
||||||
TypePackId clone(TypePackId tp, TypeArena& dest, CloneState& cloneState);
|
TypePackId clone(TypePackId tp, TypeArena& dest, CloneState& cloneState);
|
||||||
TypeId clone(TypeId tp, TypeArena& dest, CloneState& cloneState);
|
TypeId clone(TypeId tp, TypeArena& dest, CloneState& cloneState);
|
||||||
TypeFun clone(const TypeFun& typeFun, TypeArena& dest, CloneState& cloneState);
|
TypeFun clone(const TypeFun& typeFun, TypeArena& dest, CloneState& cloneState);
|
||||||
|
Binding clone(const Binding& binding, TypeArena& dest, CloneState& cloneState);
|
||||||
|
|
||||||
|
TypePackId cloneIncremental(TypePackId tp, TypeArena& dest, CloneState& cloneState, Scope* freshScopeForFreeTypes);
|
||||||
|
TypeId cloneIncremental(TypeId typeId, TypeArena& dest, CloneState& cloneState, Scope* freshScopeForFreeTypes);
|
||||||
|
TypeFun cloneIncremental(const TypeFun& typeFun, TypeArena& dest, CloneState& cloneState, Scope* freshScopeForFreeTypes);
|
||||||
|
Binding cloneIncremental(const Binding& binding, TypeArena& dest, CloneState& cloneState, Scope* freshScopeForFreeTypes);
|
||||||
|
|
||||||
} // namespace Luau
|
} // namespace Luau
|
||||||
|
|
|
@ -50,6 +50,7 @@ struct GeneralizationConstraint
|
||||||
TypeId sourceType;
|
TypeId sourceType;
|
||||||
|
|
||||||
std::vector<TypeId> interiorTypes;
|
std::vector<TypeId> interiorTypes;
|
||||||
|
bool hasDeprecatedAttribute = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// variables ~ iterate iterator
|
// variables ~ iterate iterator
|
||||||
|
@ -109,6 +110,21 @@ struct FunctionCheckConstraint
|
||||||
NotNull<DenseHashMap<const AstExpr*, TypeId>> astExpectedTypes;
|
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
|
// prim FreeType ExpectedType PrimitiveType
|
||||||
//
|
//
|
||||||
// FreeType is bounded below by the singleton type and above by PrimitiveType
|
// FreeType is bounded below by the singleton type and above by PrimitiveType
|
||||||
|
@ -273,7 +289,8 @@ using ConstraintV = Variant<
|
||||||
UnpackConstraint,
|
UnpackConstraint,
|
||||||
ReduceConstraint,
|
ReduceConstraint,
|
||||||
ReducePackConstraint,
|
ReducePackConstraint,
|
||||||
EqualityConstraint>;
|
EqualityConstraint,
|
||||||
|
TableCheckConstraint>;
|
||||||
|
|
||||||
struct Constraint
|
struct Constraint
|
||||||
{
|
{
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
#include "Luau/Ast.h"
|
#include "Luau/Ast.h"
|
||||||
#include "Luau/Constraint.h"
|
#include "Luau/Constraint.h"
|
||||||
|
#include "Luau/ConstraintSet.h"
|
||||||
#include "Luau/ControlFlow.h"
|
#include "Luau/ControlFlow.h"
|
||||||
#include "Luau/DataFlowGraph.h"
|
#include "Luau/DataFlowGraph.h"
|
||||||
#include "Luau/EqSatSimplification.h"
|
#include "Luau/EqSatSimplification.h"
|
||||||
|
@ -11,15 +12,14 @@
|
||||||
#include "Luau/ModuleResolver.h"
|
#include "Luau/ModuleResolver.h"
|
||||||
#include "Luau/Normalize.h"
|
#include "Luau/Normalize.h"
|
||||||
#include "Luau/NotNull.h"
|
#include "Luau/NotNull.h"
|
||||||
|
#include "Luau/Polarity.h"
|
||||||
#include "Luau/Refinement.h"
|
#include "Luau/Refinement.h"
|
||||||
#include "Luau/Symbol.h"
|
#include "Luau/Symbol.h"
|
||||||
#include "Luau/TypeFwd.h"
|
#include "Luau/TypeFwd.h"
|
||||||
#include "Luau/TypeUtils.h"
|
#include "Luau/TypeUtils.h"
|
||||||
#include "Luau/Variant.h"
|
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <unordered_map>
|
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
@ -92,9 +92,11 @@ struct ConstraintGenerator
|
||||||
// Constraints that go straight to the solver.
|
// Constraints that go straight to the solver.
|
||||||
std::vector<ConstraintPtr> constraints;
|
std::vector<ConstraintPtr> constraints;
|
||||||
|
|
||||||
// Constraints that do not go to the solver right away. Other constraints
|
// The set of all free types introduced during constraint generation.
|
||||||
// will enqueue them during solving.
|
DenseHashSet<TypeId> freeTypes{nullptr};
|
||||||
std::vector<ConstraintPtr> unqueuedConstraints;
|
|
||||||
|
// Map a function's signature scope back to its signature type.
|
||||||
|
DenseHashMap<Scope*, TypeId> scopeToFunction{nullptr};
|
||||||
|
|
||||||
// The private scope of type aliases for which the type parameters belong to.
|
// The private scope of type aliases for which the type parameters belong to.
|
||||||
DenseHashMap<const AstStatTypeAlias*, ScopePtr> astTypeAliasDefiningScopes{nullptr};
|
DenseHashMap<const AstStatTypeAlias*, ScopePtr> astTypeAliasDefiningScopes{nullptr};
|
||||||
|
@ -114,18 +116,23 @@ struct ConstraintGenerator
|
||||||
|
|
||||||
// Needed to register all available type functions for execution at later stages.
|
// Needed to register all available type functions for execution at later stages.
|
||||||
NotNull<TypeFunctionRuntime> typeFunctionRuntime;
|
NotNull<TypeFunctionRuntime> typeFunctionRuntime;
|
||||||
|
DenseHashMap<const AstStatTypeFunction*, ScopePtr> astTypeFunctionEnvironmentScopes{nullptr};
|
||||||
|
|
||||||
// Needed to resolve modules to make 'require' import types properly.
|
// Needed to resolve modules to make 'require' import types properly.
|
||||||
NotNull<ModuleResolver> moduleResolver;
|
NotNull<ModuleResolver> moduleResolver;
|
||||||
// Occasionally constraint generation needs to produce an ICE.
|
// Occasionally constraint generation needs to produce an ICE.
|
||||||
const NotNull<InternalErrorReporter> ice;
|
const NotNull<InternalErrorReporter> ice;
|
||||||
|
|
||||||
ScopePtr globalScope;
|
ScopePtr globalScope;
|
||||||
|
ScopePtr typeFunctionScope;
|
||||||
|
|
||||||
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope;
|
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope;
|
||||||
std::vector<RequireCycle> requireCycles;
|
std::vector<RequireCycle> requireCycles;
|
||||||
|
|
||||||
DenseHashMap<TypeId, TypeIds> localTypes{nullptr};
|
DenseHashMap<TypeId, TypeIds> localTypes{nullptr};
|
||||||
|
|
||||||
|
DenseHashMap<AstExpr*, Inference> inferredExprCache{nullptr};
|
||||||
|
|
||||||
DcrLogger* logger;
|
DcrLogger* logger;
|
||||||
|
|
||||||
ConstraintGenerator(
|
ConstraintGenerator(
|
||||||
|
@ -137,12 +144,16 @@ struct ConstraintGenerator
|
||||||
NotNull<BuiltinTypes> builtinTypes,
|
NotNull<BuiltinTypes> builtinTypes,
|
||||||
NotNull<InternalErrorReporter> ice,
|
NotNull<InternalErrorReporter> ice,
|
||||||
const ScopePtr& globalScope,
|
const ScopePtr& globalScope,
|
||||||
|
const ScopePtr& typeFunctionScope,
|
||||||
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope,
|
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope,
|
||||||
DcrLogger* logger,
|
DcrLogger* logger,
|
||||||
NotNull<DataFlowGraph> dfg,
|
NotNull<DataFlowGraph> dfg,
|
||||||
std::vector<RequireCycle> requireCycles
|
std::vector<RequireCycle> requireCycles
|
||||||
);
|
);
|
||||||
|
|
||||||
|
ConstraintSet run(AstStatBlock* block);
|
||||||
|
ConstraintSet runOnFragment(const ScopePtr& resumeScope, AstStatBlock* block);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The entry point to the ConstraintGenerator. This will construct a set
|
* The entry point to the ConstraintGenerator. This will construct a set
|
||||||
* of scopes, constraints, and free types that can be solved later.
|
* of scopes, constraints, and free types that can be solved later.
|
||||||
|
@ -153,19 +164,26 @@ struct ConstraintGenerator
|
||||||
void visitFragmentRoot(const ScopePtr& resumeScope, AstStatBlock* block);
|
void visitFragmentRoot(const ScopePtr& resumeScope, AstStatBlock* block);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::vector<std::vector<TypeId>> interiorTypes;
|
struct InteriorFreeTypes
|
||||||
|
{
|
||||||
|
std::vector<TypeId> types;
|
||||||
|
std::vector<TypePackId> typePacks;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<std::vector<TypeId>> DEPRECATED_interiorTypes;
|
||||||
|
std::vector<InteriorFreeTypes> interiorFreeTypes;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fabricates a new free type belonging to a given scope.
|
* Fabricates a new free type belonging to a given scope.
|
||||||
* @param scope the scope the free type belongs to.
|
* @param scope the scope the free type belongs to.
|
||||||
*/
|
*/
|
||||||
TypeId freshType(const ScopePtr& scope);
|
TypeId freshType(const ScopePtr& scope, Polarity polarity = Polarity::Unknown);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fabricates a new free type pack belonging to a given scope.
|
* Fabricates a new free type pack belonging to a given scope.
|
||||||
* @param scope the scope the free type pack belongs to.
|
* @param scope the scope the free type pack belongs to.
|
||||||
*/
|
*/
|
||||||
TypePackId freshTypePack(const ScopePtr& scope);
|
TypePackId freshTypePack(const ScopePtr& scope, Polarity polarity = Polarity::Unknown);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allocate a new TypePack with the given head and tail.
|
* Allocate a new TypePack with the given head and tail.
|
||||||
|
@ -254,7 +272,7 @@ private:
|
||||||
ControlFlow visit(const ScopePtr& scope, AstStatTypeAlias* alias);
|
ControlFlow visit(const ScopePtr& scope, AstStatTypeAlias* alias);
|
||||||
ControlFlow visit(const ScopePtr& scope, AstStatTypeFunction* function);
|
ControlFlow visit(const ScopePtr& scope, AstStatTypeFunction* function);
|
||||||
ControlFlow visit(const ScopePtr& scope, AstStatDeclareGlobal* declareGlobal);
|
ControlFlow visit(const ScopePtr& scope, AstStatDeclareGlobal* declareGlobal);
|
||||||
ControlFlow visit(const ScopePtr& scope, AstStatDeclareClass* declareClass);
|
ControlFlow visit(const ScopePtr& scope, AstStatDeclareExternType* declareExternType);
|
||||||
ControlFlow visit(const ScopePtr& scope, AstStatDeclareFunction* declareFunction);
|
ControlFlow visit(const ScopePtr& scope, AstStatDeclareFunction* declareFunction);
|
||||||
ControlFlow visit(const ScopePtr& scope, AstStatError* error);
|
ControlFlow visit(const ScopePtr& scope, AstStatError* error);
|
||||||
|
|
||||||
|
@ -286,7 +304,7 @@ private:
|
||||||
);
|
);
|
||||||
|
|
||||||
Inference check(const ScopePtr& scope, AstExprConstantString* string, std::optional<TypeId> expectedType, bool forceSingleton);
|
Inference check(const ScopePtr& scope, AstExprConstantString* string, std::optional<TypeId> expectedType, bool forceSingleton);
|
||||||
Inference check(const ScopePtr& scope, AstExprConstantBool* bool_, std::optional<TypeId> expectedType, bool forceSingleton);
|
Inference check(const ScopePtr& scope, AstExprConstantBool* boolExpr, std::optional<TypeId> expectedType, bool forceSingleton);
|
||||||
Inference check(const ScopePtr& scope, AstExprLocal* local);
|
Inference check(const ScopePtr& scope, AstExprLocal* local);
|
||||||
Inference check(const ScopePtr& scope, AstExprGlobal* global);
|
Inference check(const ScopePtr& scope, AstExprGlobal* global);
|
||||||
Inference checkIndexName(const ScopePtr& scope, const RefinementKey* key, AstExpr* indexee, const std::string& index, Location indexLocation);
|
Inference checkIndexName(const ScopePtr& scope, const RefinementKey* key, AstExpr* indexee, const std::string& index, Location indexLocation);
|
||||||
|
@ -295,11 +313,25 @@ private:
|
||||||
Inference check(const ScopePtr& scope, AstExprFunction* func, std::optional<TypeId> expectedType, bool generalize);
|
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, AstExprUnary* unary);
|
||||||
Inference check(const ScopePtr& scope, AstExprBinary* binary, std::optional<TypeId> expectedType);
|
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, AstExprIfElse* ifElse, std::optional<TypeId> expectedType);
|
||||||
Inference check(const ScopePtr& scope, AstExprTypeAssertion* typeAssert);
|
Inference check(const ScopePtr& scope, AstExprTypeAssertion* typeAssert);
|
||||||
Inference check(const ScopePtr& scope, AstExprInterpString* interpString);
|
Inference check(const ScopePtr& scope, AstExprInterpString* interpString);
|
||||||
Inference check(const ScopePtr& scope, AstExprTable* expr, std::optional<TypeId> expectedType);
|
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, AstExpr* expr, TypeId rhsType);
|
||||||
void visitLValue(const ScopePtr& scope, AstExprLocal* local, TypeId rhsType);
|
void visitLValue(const ScopePtr& scope, AstExprLocal* local, TypeId rhsType);
|
||||||
|
@ -348,6 +380,11 @@ private:
|
||||||
**/
|
**/
|
||||||
TypeId resolveType(const ScopePtr& scope, AstType* ty, bool inTypeArguments, bool replaceErrorWithFresh = false);
|
TypeId resolveType(const ScopePtr& scope, AstType* ty, bool inTypeArguments, bool replaceErrorWithFresh = false);
|
||||||
|
|
||||||
|
// resolveType() is recursive, but we only want to invoke
|
||||||
|
// inferGenericPolarities() once at the very end. We thus isolate the
|
||||||
|
// recursive part of the algorithm to this internal helper.
|
||||||
|
TypeId resolveType_(const ScopePtr& scope, AstType* ty, bool inTypeArguments, bool replaceErrorWithFresh = false);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolves a type pack from its AST annotation.
|
* Resolves a type pack from its AST annotation.
|
||||||
* @param scope the scope that the type annotation appears within.
|
* @param scope the scope that the type annotation appears within.
|
||||||
|
@ -357,6 +394,9 @@ private:
|
||||||
**/
|
**/
|
||||||
TypePackId resolveTypePack(const ScopePtr& scope, AstTypePack* tp, bool inTypeArguments, bool replaceErrorWithFresh = false);
|
TypePackId resolveTypePack(const ScopePtr& scope, AstTypePack* tp, bool inTypeArguments, bool replaceErrorWithFresh = false);
|
||||||
|
|
||||||
|
// Inner hepler for resolveTypePack
|
||||||
|
TypePackId resolveTypePack_(const ScopePtr& scope, AstTypePack* tp, bool inTypeArguments, bool replaceErrorWithFresh = false);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolves a type pack from its AST annotation.
|
* Resolves a type pack from its AST annotation.
|
||||||
* @param scope the scope that the type annotation appears within.
|
* @param scope the scope that the type annotation appears within.
|
||||||
|
@ -378,7 +418,7 @@ private:
|
||||||
**/
|
**/
|
||||||
std::vector<std::pair<Name, GenericTypeDefinition>> createGenerics(
|
std::vector<std::pair<Name, GenericTypeDefinition>> createGenerics(
|
||||||
const ScopePtr& scope,
|
const ScopePtr& scope,
|
||||||
AstArray<AstGenericType> generics,
|
AstArray<AstGenericType*> generics,
|
||||||
bool useCache = false,
|
bool useCache = false,
|
||||||
bool addTypes = true
|
bool addTypes = true
|
||||||
);
|
);
|
||||||
|
@ -395,7 +435,7 @@ private:
|
||||||
**/
|
**/
|
||||||
std::vector<std::pair<Name, GenericTypePackDefinition>> createGenericPacks(
|
std::vector<std::pair<Name, GenericTypePackDefinition>> createGenericPacks(
|
||||||
const ScopePtr& scope,
|
const ScopePtr& scope,
|
||||||
AstArray<AstGenericTypePack> packs,
|
AstArray<AstGenericTypePack*> generics,
|
||||||
bool useCache = false,
|
bool useCache = false,
|
||||||
bool addTypes = true
|
bool addTypes = true
|
||||||
);
|
);
|
||||||
|
@ -444,9 +484,4 @@ private:
|
||||||
TypeId simplifyUnion(const ScopePtr& scope, Location location, TypeId left, TypeId right);
|
TypeId simplifyUnion(const ScopePtr& scope, Location location, TypeId left, TypeId right);
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Borrow a vector of pointers from a vector of owning pointers to constraints.
|
|
||||||
*/
|
|
||||||
std::vector<NotNull<Constraint>> borrowConstraints(const std::vector<ConstraintPtr>& constraints);
|
|
||||||
|
|
||||||
|
|
||||||
} // namespace Luau
|
} // namespace Luau
|
||||||
|
|
32
Analysis/include/Luau/ConstraintSet.h
Normal file
32
Analysis/include/Luau/ConstraintSet.h
Normal 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;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
|
@ -3,6 +3,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Luau/Constraint.h"
|
#include "Luau/Constraint.h"
|
||||||
|
#include "Luau/ConstraintSet.h"
|
||||||
#include "Luau/DataFlowGraph.h"
|
#include "Luau/DataFlowGraph.h"
|
||||||
#include "Luau/DenseHash.h"
|
#include "Luau/DenseHash.h"
|
||||||
#include "Luau/EqSatSimplification.h"
|
#include "Luau/EqSatSimplification.h"
|
||||||
|
@ -59,6 +60,25 @@ struct HashInstantiationSignature
|
||||||
size_t operator()(const InstantiationSignature& signature) const;
|
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
|
struct ConstraintSolver
|
||||||
{
|
{
|
||||||
NotNull<TypeArena> arena;
|
NotNull<TypeArena> arena;
|
||||||
|
@ -68,7 +88,9 @@ struct ConstraintSolver
|
||||||
NotNull<Simplifier> simplifier;
|
NotNull<Simplifier> simplifier;
|
||||||
NotNull<TypeFunctionRuntime> typeFunctionRuntime;
|
NotNull<TypeFunctionRuntime> typeFunctionRuntime;
|
||||||
// The entire set of constraints that the solver is trying to resolve.
|
// The entire set of constraints that the solver is trying to resolve.
|
||||||
|
ConstraintSet constraintSet;
|
||||||
std::vector<NotNull<Constraint>> constraints;
|
std::vector<NotNull<Constraint>> constraints;
|
||||||
|
NotNull<DenseHashMap<Scope*, TypeId>> scopeToFunction;
|
||||||
NotNull<Scope> rootScope;
|
NotNull<Scope> rootScope;
|
||||||
ModuleName currentModuleName;
|
ModuleName currentModuleName;
|
||||||
|
|
||||||
|
@ -99,6 +121,9 @@ struct ConstraintSolver
|
||||||
// A mapping from free types to the number of unresolved constraints that mention them.
|
// A mapping from free types to the number of unresolved constraints that mention them.
|
||||||
DenseHashMap<TypeId, size_t> unresolvedConstraints{{}};
|
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.
|
// Irreducible/uninhabited type functions or type pack functions.
|
||||||
DenseHashSet<const void*> uninhabitedTypeFunctions{{}};
|
DenseHashSet<const void*> uninhabitedTypeFunctions{{}};
|
||||||
|
|
||||||
|
@ -117,12 +142,26 @@ struct ConstraintSolver
|
||||||
|
|
||||||
DenseHashMap<TypeId, const Constraint*> typeFunctionsToFinalize{nullptr};
|
DenseHashMap<TypeId, const Constraint*> typeFunctionsToFinalize{nullptr};
|
||||||
|
|
||||||
|
explicit ConstraintSolver(
|
||||||
|
NotNull<Normalizer> normalizer,
|
||||||
|
NotNull<Simplifier> simplifier,
|
||||||
|
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
|
||||||
|
ModuleName moduleName,
|
||||||
|
NotNull<ModuleResolver> moduleResolver,
|
||||||
|
std::vector<RequireCycle> requireCycles,
|
||||||
|
DcrLogger* logger,
|
||||||
|
NotNull<const DataFlowGraph> dfg,
|
||||||
|
TypeCheckLimits limits,
|
||||||
|
ConstraintSet constraintSet
|
||||||
|
);
|
||||||
|
|
||||||
explicit ConstraintSolver(
|
explicit ConstraintSolver(
|
||||||
NotNull<Normalizer> normalizer,
|
NotNull<Normalizer> normalizer,
|
||||||
NotNull<Simplifier> simplifier,
|
NotNull<Simplifier> simplifier,
|
||||||
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
|
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
|
||||||
NotNull<Scope> rootScope,
|
NotNull<Scope> rootScope,
|
||||||
std::vector<NotNull<Constraint>> constraints,
|
std::vector<NotNull<Constraint>> constraints,
|
||||||
|
NotNull<DenseHashMap<Scope*, TypeId>> scopeToFunction,
|
||||||
ModuleName moduleName,
|
ModuleName moduleName,
|
||||||
NotNull<ModuleResolver> moduleResolver,
|
NotNull<ModuleResolver> moduleResolver,
|
||||||
std::vector<RequireCycle> requireCycles,
|
std::vector<RequireCycle> requireCycles,
|
||||||
|
@ -147,9 +186,14 @@ struct ConstraintSolver
|
||||||
**/
|
**/
|
||||||
void finalizeTypeFunctions();
|
void finalizeTypeFunctions();
|
||||||
|
|
||||||
bool isDone();
|
bool isDone() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
/// A helper that does most of the setup work that is shared between the two constructors.
|
||||||
|
void initFreeTypeTracking();
|
||||||
|
|
||||||
|
void generalizeOneType(TypeId ty);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bind a type variable to another type.
|
* Bind a type variable to another type.
|
||||||
*
|
*
|
||||||
|
@ -182,6 +226,7 @@ public:
|
||||||
bool tryDispatch(const NameConstraint& c, NotNull<const Constraint> constraint);
|
bool tryDispatch(const NameConstraint& c, NotNull<const Constraint> constraint);
|
||||||
bool tryDispatch(const TypeAliasExpansionConstraint& 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 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 FunctionCheckConstraint& c, NotNull<const Constraint> constraint);
|
||||||
bool tryDispatch(const PrimitiveTypeConstraint& c, NotNull<const Constraint> constraint);
|
bool tryDispatch(const PrimitiveTypeConstraint& c, NotNull<const Constraint> constraint);
|
||||||
bool tryDispatch(const HasPropConstraint& c, NotNull<const Constraint> constraint);
|
bool tryDispatch(const HasPropConstraint& c, NotNull<const Constraint> constraint);
|
||||||
|
@ -211,7 +256,7 @@ public:
|
||||||
// for a, ... in next_function, t, ... do
|
// for a, ... in next_function, t, ... do
|
||||||
bool tryDispatchIterableFunction(TypeId nextTy, TypeId tableTy, const IterableConstraint& c, NotNull<const Constraint> constraint);
|
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,
|
NotNull<const Constraint> constraint,
|
||||||
TypeId subjectType,
|
TypeId subjectType,
|
||||||
const std::string& propName,
|
const std::string& propName,
|
||||||
|
@ -219,7 +264,8 @@ public:
|
||||||
bool inConditional = false,
|
bool inConditional = false,
|
||||||
bool suppressSimplification = false
|
bool suppressSimplification = false
|
||||||
);
|
);
|
||||||
std::pair<std::vector<TypeId>, std::optional<TypeId>> lookupTableProp(
|
|
||||||
|
TablePropLookupResult lookupTableProp(
|
||||||
NotNull<const Constraint> constraint,
|
NotNull<const Constraint> constraint,
|
||||||
TypeId subjectType,
|
TypeId subjectType,
|
||||||
const std::string& propName,
|
const std::string& propName,
|
||||||
|
@ -278,10 +324,10 @@ public:
|
||||||
// FIXME: This use of a boolean for the return result is an appalling
|
// FIXME: This use of a boolean for the return result is an appalling
|
||||||
// interface.
|
// interface.
|
||||||
bool blockOnPendingTypes(TypeId target, NotNull<const Constraint> constraint);
|
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(NotNull<const Constraint> progressed);
|
||||||
void unblock(TypeId progressed, Location location);
|
void unblock(TypeId ty, Location location);
|
||||||
void unblock(TypePackId progressed, Location location);
|
void unblock(TypePackId progressed, Location location);
|
||||||
void unblock(const std::vector<TypeId>& types, Location location);
|
void unblock(const std::vector<TypeId>& types, Location location);
|
||||||
void unblock(const std::vector<TypePackId>& packs, Location location);
|
void unblock(const std::vector<TypePackId>& packs, Location location);
|
||||||
|
@ -316,7 +362,7 @@ public:
|
||||||
* @param location the location where the require is taking place; used for
|
* @param location the location where the require is taking place; used for
|
||||||
* error locations.
|
* 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(TypeErrorData&& data, const Location& location);
|
||||||
void reportError(TypeError e);
|
void reportError(TypeError e);
|
||||||
|
@ -337,7 +383,7 @@ public:
|
||||||
* @returns a non-free type that generalizes the argument, or `std::nullopt` if one
|
* @returns a non-free type that generalizes the argument, or `std::nullopt` if one
|
||||||
* does not exist
|
* does not exist
|
||||||
*/
|
*/
|
||||||
std::optional<TypeId> generalizeFreeType(NotNull<Scope> scope, TypeId type, bool avoidSealingTables = false);
|
std::optional<TypeId> generalizeFreeType(NotNull<Scope> scope, TypeId type);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks the existing set of constraints to see if there exist any that contain
|
* Checks the existing set of constraints to see if there exist any that contain
|
||||||
|
@ -400,8 +446,14 @@ public:
|
||||||
void throwUserCancelError() const;
|
void throwUserCancelError() const;
|
||||||
|
|
||||||
ToStringOptions opts;
|
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);
|
void dump(NotNull<Scope> rootScope, struct ToStringOptions& opts);
|
||||||
|
|
||||||
} // namespace Luau
|
} // namespace Luau
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
#include "Luau/ControlFlow.h"
|
#include "Luau/ControlFlow.h"
|
||||||
#include "Luau/DenseHash.h"
|
#include "Luau/DenseHash.h"
|
||||||
#include "Luau/Def.h"
|
#include "Luau/Def.h"
|
||||||
|
#include "Luau/NotNull.h"
|
||||||
#include "Luau/Symbol.h"
|
#include "Luau/Symbol.h"
|
||||||
#include "Luau/TypedAllocator.h"
|
#include "Luau/TypedAllocator.h"
|
||||||
|
|
||||||
|
@ -37,8 +38,6 @@ struct DataFlowGraph
|
||||||
DefId getDef(const AstExpr* expr) const;
|
DefId getDef(const AstExpr* expr) const;
|
||||||
// Look up the definition optionally, knowing it may not be present.
|
// Look up the definition optionally, knowing it may not be present.
|
||||||
std::optional<DefId> getDefOptional(const AstExpr* expr) const;
|
std::optional<DefId> getDefOptional(const AstExpr* expr) const;
|
||||||
// Look up for the rvalue def for a compound assignment.
|
|
||||||
std::optional<DefId> getRValueDefForCompoundAssign(const AstExpr* expr) const;
|
|
||||||
|
|
||||||
DefId getDef(const AstLocal* local) const;
|
DefId getDef(const AstLocal* local) const;
|
||||||
|
|
||||||
|
@ -48,13 +47,13 @@ struct DataFlowGraph
|
||||||
const RefinementKey* getRefinementKey(const AstExpr* expr) const;
|
const RefinementKey* getRefinementKey(const AstExpr* expr) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
DataFlowGraph() = default;
|
DataFlowGraph(NotNull<DefArena> defArena, NotNull<RefinementKeyArena> keyArena);
|
||||||
|
|
||||||
DataFlowGraph(const DataFlowGraph&) = delete;
|
DataFlowGraph(const DataFlowGraph&) = delete;
|
||||||
DataFlowGraph& operator=(const DataFlowGraph&) = delete;
|
DataFlowGraph& operator=(const DataFlowGraph&) = delete;
|
||||||
|
|
||||||
DefArena defArena;
|
NotNull<DefArena> defArena;
|
||||||
RefinementKeyArena keyArena;
|
NotNull<RefinementKeyArena> keyArena;
|
||||||
|
|
||||||
DenseHashMap<const AstExpr*, const Def*> astDefs{nullptr};
|
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.
|
// All keys in this maps are really only statements that ambiently declares a symbol.
|
||||||
DenseHashMap<const AstStat*, const Def*> declaredDefs{nullptr};
|
DenseHashMap<const AstStat*, const Def*> declaredDefs{nullptr};
|
||||||
|
|
||||||
// Compound assignments are in a weird situation where the local being assigned to is also being used at its
|
|
||||||
// previous type implicitly in an rvalue position. This map provides the previous binding.
|
|
||||||
DenseHashMap<const AstExpr*, const Def*> compoundAssignDefs{nullptr};
|
|
||||||
|
|
||||||
DenseHashMap<const AstExpr*, const RefinementKey*> astRefinementKeys{nullptr};
|
DenseHashMap<const AstExpr*, const RefinementKey*> astRefinementKeys{nullptr};
|
||||||
friend struct DataFlowGraphBuilder;
|
friend struct DataFlowGraphBuilder;
|
||||||
};
|
};
|
||||||
|
@ -84,7 +79,6 @@ struct DfgScope
|
||||||
|
|
||||||
DfgScope* parent;
|
DfgScope* parent;
|
||||||
ScopeType scopeType;
|
ScopeType scopeType;
|
||||||
Location location;
|
|
||||||
|
|
||||||
using Bindings = DenseHashMap<Symbol, const Def*>;
|
using Bindings = DenseHashMap<Symbol, const Def*>;
|
||||||
using Props = DenseHashMap<const Def*, std::unordered_map<std::string, 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
|
struct DataFlowGraphBuilder
|
||||||
{
|
{
|
||||||
static DataFlowGraph build(AstStatBlock* root, NotNull<struct InternalErrorReporter> handle);
|
static DataFlowGraph build(
|
||||||
|
|
||||||
/**
|
|
||||||
* 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(
|
|
||||||
AstStatBlock* block,
|
AstStatBlock* block,
|
||||||
NotNull<InternalErrorReporter> handle
|
NotNull<DefArena> defArena,
|
||||||
);
|
NotNull<RefinementKeyArena> keyArena,
|
||||||
|
NotNull<struct 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
|
|
||||||
);
|
);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
DataFlowGraphBuilder() = default;
|
DataFlowGraphBuilder(NotNull<DefArena> defArena, NotNull<RefinementKeyArena> keyArena);
|
||||||
|
|
||||||
DataFlowGraphBuilder(const DataFlowGraphBuilder&) = delete;
|
DataFlowGraphBuilder(const DataFlowGraphBuilder&) = delete;
|
||||||
DataFlowGraphBuilder& operator=(const DataFlowGraphBuilder&) = delete;
|
DataFlowGraphBuilder& operator=(const DataFlowGraphBuilder&) = delete;
|
||||||
|
|
||||||
DataFlowGraph graph;
|
DataFlowGraph graph;
|
||||||
NotNull<DefArena> defArena{&graph.defArena};
|
NotNull<DefArena> defArena;
|
||||||
NotNull<RefinementKeyArena> keyArena{&graph.keyArena};
|
NotNull<RefinementKeyArena> keyArena;
|
||||||
|
|
||||||
struct InternalErrorReporter* handle = nullptr;
|
struct InternalErrorReporter* handle = nullptr;
|
||||||
|
|
||||||
|
@ -162,8 +129,8 @@ private:
|
||||||
|
|
||||||
/// A stack of scopes used by the visitor to see where we are.
|
/// A stack of scopes used by the visitor to see where we are.
|
||||||
ScopeStack scopeStack;
|
ScopeStack scopeStack;
|
||||||
|
NotNull<DfgScope> currentScope();
|
||||||
DfgScope* currentScope();
|
DfgScope* currentScope_DEPRECATED();
|
||||||
|
|
||||||
struct FunctionCapture
|
struct FunctionCapture
|
||||||
{
|
{
|
||||||
|
@ -175,14 +142,14 @@ private:
|
||||||
DenseHashMap<Symbol, FunctionCapture> captures{Symbol{}};
|
DenseHashMap<Symbol, FunctionCapture> captures{Symbol{}};
|
||||||
void resolveCaptures();
|
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 join(DfgScope* p, DfgScope* a, DfgScope* b);
|
||||||
void joinBindings(DfgScope* p, const DfgScope& a, const DfgScope& b);
|
void joinBindings(DfgScope* p, const DfgScope& a, const DfgScope& b);
|
||||||
void joinProps(DfgScope* p, const DfgScope& a, const DfgScope& b);
|
void joinProps(DfgScope* p, const DfgScope& a, const DfgScope& b);
|
||||||
|
|
||||||
DefId lookup(Symbol symbol);
|
DefId lookup(Symbol symbol, Location location);
|
||||||
DefId lookup(DefId def, const std::string& key);
|
DefId lookup(DefId def, const std::string& key, Location location);
|
||||||
|
|
||||||
ControlFlow visit(AstStatBlock* b);
|
ControlFlow visit(AstStatBlock* b);
|
||||||
ControlFlow visitBlockWithoutChildScope(AstStatBlock* b);
|
ControlFlow visitBlockWithoutChildScope(AstStatBlock* b);
|
||||||
|
@ -206,7 +173,7 @@ private:
|
||||||
ControlFlow visit(AstStatTypeFunction* f);
|
ControlFlow visit(AstStatTypeFunction* f);
|
||||||
ControlFlow visit(AstStatDeclareGlobal* d);
|
ControlFlow visit(AstStatDeclareGlobal* d);
|
||||||
ControlFlow visit(AstStatDeclareFunction* d);
|
ControlFlow visit(AstStatDeclareFunction* d);
|
||||||
ControlFlow visit(AstStatDeclareClass* d);
|
ControlFlow visit(AstStatDeclareExternType* d);
|
||||||
ControlFlow visit(AstStatError* error);
|
ControlFlow visit(AstStatError* error);
|
||||||
|
|
||||||
DataFlowResult visitExpr(AstExpr* e);
|
DataFlowResult visitExpr(AstExpr* e);
|
||||||
|
@ -248,8 +215,8 @@ private:
|
||||||
|
|
||||||
void visitTypeList(AstTypeList l);
|
void visitTypeList(AstTypeList l);
|
||||||
|
|
||||||
void visitGenerics(AstArray<AstGenericType> g);
|
void visitGenerics(AstArray<AstGenericType*> g);
|
||||||
void visitGenericPacks(AstArray<AstGenericTypePack> g);
|
void visitGenericPacks(AstArray<AstGenericTypePack*> g);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Luau
|
} // namespace Luau
|
||||||
|
|
|
@ -4,7 +4,8 @@
|
||||||
#include "Luau/NotNull.h"
|
#include "Luau/NotNull.h"
|
||||||
#include "Luau/TypedAllocator.h"
|
#include "Luau/TypedAllocator.h"
|
||||||
#include "Luau/Variant.h"
|
#include "Luau/Variant.h"
|
||||||
|
#include "Luau/Location.h"
|
||||||
|
#include "Luau/Symbol.h"
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
||||||
|
@ -13,6 +14,7 @@ namespace Luau
|
||||||
|
|
||||||
struct Def;
|
struct Def;
|
||||||
using DefId = NotNull<const Def>;
|
using DefId = NotNull<const Def>;
|
||||||
|
struct AstLocal;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A cell is a "single-object" value.
|
* A cell is a "single-object" value.
|
||||||
|
@ -64,6 +66,8 @@ struct Def
|
||||||
using V = Variant<struct Cell, struct Phi>;
|
using V = Variant<struct Cell, struct Phi>;
|
||||||
|
|
||||||
V v;
|
V v;
|
||||||
|
Symbol name;
|
||||||
|
Location location;
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
|
@ -79,7 +83,7 @@ struct DefArena
|
||||||
{
|
{
|
||||||
TypedAllocator<Def> allocator;
|
TypedAllocator<Def> allocator;
|
||||||
|
|
||||||
DefId freshCell(bool subscripted = false);
|
DefId freshCell(Symbol sym, Location location, bool subscripted = false);
|
||||||
DefId phi(DefId a, DefId b);
|
DefId phi(DefId a, DefId b);
|
||||||
DefId phi(const std::vector<DefId>& defs);
|
DefId phi(const std::vector<DefId>& defs);
|
||||||
};
|
};
|
||||||
|
|
|
@ -53,7 +53,7 @@ LUAU_EQSAT_NODE_SET(Intersection);
|
||||||
|
|
||||||
LUAU_EQSAT_NODE_ARRAY(Negation, 1);
|
LUAU_EQSAT_NODE_ARRAY(Negation, 1);
|
||||||
|
|
||||||
LUAU_EQSAT_NODE_ATOM_WITH_VECTOR(TTypeFun, const TypeFunction*);
|
LUAU_EQSAT_NODE_ATOM_WITH_VECTOR(TTypeFun, std::shared_ptr<const TypeFunctionInstanceType>);
|
||||||
|
|
||||||
LUAU_EQSAT_UNIT(TNoRefine);
|
LUAU_EQSAT_UNIT(TNoRefine);
|
||||||
LUAU_EQSAT_UNIT(Invalid);
|
LUAU_EQSAT_UNIT(Invalid);
|
||||||
|
@ -105,6 +105,9 @@ private:
|
||||||
std::vector<Id> storage;
|
std::vector<Id> storage;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template<typename L>
|
||||||
|
using Node = EqSat::Node<L>;
|
||||||
|
|
||||||
using EType = EqSat::Language<
|
using EType = EqSat::Language<
|
||||||
TNil,
|
TNil,
|
||||||
TBoolean,
|
TBoolean,
|
||||||
|
@ -146,7 +149,7 @@ using EType = EqSat::Language<
|
||||||
struct StringCache
|
struct StringCache
|
||||||
{
|
{
|
||||||
Allocator allocator;
|
Allocator allocator;
|
||||||
DenseHashMap<size_t, StringId> strings{{}};
|
DenseHashMap<std::string_view, StringId> strings{{}};
|
||||||
std::vector<std::string_view> views;
|
std::vector<std::string_view> views;
|
||||||
|
|
||||||
StringId add(std::string_view s);
|
StringId add(std::string_view s);
|
||||||
|
@ -171,6 +174,9 @@ struct Subst
|
||||||
Id eclass;
|
Id eclass;
|
||||||
Id newClass;
|
Id newClass;
|
||||||
|
|
||||||
|
// The node into eclass which is boring, if any
|
||||||
|
std::optional<size_t> boringIndex;
|
||||||
|
|
||||||
std::string desc;
|
std::string desc;
|
||||||
|
|
||||||
Subst(Id eclass, Id newClass, std::string desc = "");
|
Subst(Id eclass, Id newClass, std::string desc = "");
|
||||||
|
@ -211,6 +217,7 @@ struct Simplifier
|
||||||
void subst(Id from, Id to);
|
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);
|
||||||
void subst(Id from, Id to, const std::string& ruleName, const std::unordered_map<Id, size_t>& forceNodes);
|
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);
|
void unionClasses(std::vector<Id>& hereParts, Id there);
|
||||||
|
|
||||||
|
@ -218,6 +225,7 @@ struct Simplifier
|
||||||
void simplifyUnion(Id id);
|
void simplifyUnion(Id id);
|
||||||
void uninhabitedIntersection(Id id);
|
void uninhabitedIntersection(Id id);
|
||||||
void intersectWithNegatedClass(Id id);
|
void intersectWithNegatedClass(Id id);
|
||||||
|
void intersectWithNegatedAtom(Id id);
|
||||||
void intersectWithNoRefine(Id id);
|
void intersectWithNoRefine(Id id);
|
||||||
void cyclicIntersectionOfUnion(Id id);
|
void cyclicIntersectionOfUnion(Id id);
|
||||||
void cyclicUnionOfIntersection(Id id);
|
void cyclicUnionOfIntersection(Id id);
|
||||||
|
@ -228,6 +236,7 @@ struct Simplifier
|
||||||
void unneededTableModification(Id id);
|
void unneededTableModification(Id id);
|
||||||
void builtinTypeFunctions(Id id);
|
void builtinTypeFunctions(Id id);
|
||||||
void iffyTypeFunctions(Id id);
|
void iffyTypeFunctions(Id id);
|
||||||
|
void strictMetamethods(Id id);
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename Tag>
|
template<typename Tag>
|
||||||
|
@ -293,13 +302,13 @@ QueryIterator<Tag>::QueryIterator(EGraph* egraph_, Id eclass)
|
||||||
|
|
||||||
for (const auto& enode : ecl.nodes)
|
for (const auto& enode : ecl.nodes)
|
||||||
{
|
{
|
||||||
if (enode.index() < idx)
|
if (enode.node.index() < idx)
|
||||||
++index;
|
++index;
|
||||||
else
|
else
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (index >= ecl.nodes.size() || ecl.nodes[index].index() != idx)
|
if (index >= ecl.nodes.size() || ecl.nodes[index].node.index() != idx)
|
||||||
{
|
{
|
||||||
egraph = nullptr;
|
egraph = nullptr;
|
||||||
index = 0;
|
index = 0;
|
||||||
|
@ -329,7 +338,7 @@ std::pair<const Tag*, size_t> QueryIterator<Tag>::operator*() const
|
||||||
EGraph::EClassT& ecl = (*egraph)[eclass];
|
EGraph::EClassT& ecl = (*egraph)[eclass];
|
||||||
|
|
||||||
LUAU_ASSERT(index < ecl.nodes.size());
|
LUAU_ASSERT(index < ecl.nodes.size());
|
||||||
auto& enode = ecl.nodes[index];
|
auto& enode = ecl.nodes[index].node;
|
||||||
Tag* result = enode.template get<Tag>();
|
Tag* result = enode.template get<Tag>();
|
||||||
LUAU_ASSERT(result);
|
LUAU_ASSERT(result);
|
||||||
return {result, index};
|
return {result, index};
|
||||||
|
@ -341,12 +350,16 @@ QueryIterator<Tag>& QueryIterator<Tag>::operator++()
|
||||||
{
|
{
|
||||||
const auto& ecl = (*egraph)[eclass];
|
const auto& ecl = (*egraph)[eclass];
|
||||||
|
|
||||||
++index;
|
do
|
||||||
if (index >= ecl.nodes.size() || ecl.nodes[index].index() != EType::VariantTy::getTypeId<Tag>())
|
|
||||||
{
|
{
|
||||||
egraph = nullptr;
|
++index;
|
||||||
index = 0;
|
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;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
|
@ -332,11 +332,11 @@ struct TypePackMismatch
|
||||||
bool operator==(const TypePackMismatch& rhs) const;
|
bool operator==(const TypePackMismatch& rhs) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct DynamicPropertyLookupOnClassesUnsafe
|
struct DynamicPropertyLookupOnExternTypesUnsafe
|
||||||
{
|
{
|
||||||
TypeId ty;
|
TypeId ty;
|
||||||
|
|
||||||
bool operator==(const DynamicPropertyLookupOnClassesUnsafe& rhs) const;
|
bool operator==(const DynamicPropertyLookupOnExternTypesUnsafe& rhs) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct UninhabitedTypeFunction
|
struct UninhabitedTypeFunction
|
||||||
|
@ -455,6 +455,13 @@ struct UserDefinedTypeFunctionError
|
||||||
bool operator==(const UserDefinedTypeFunctionError& rhs) const;
|
bool operator==(const UserDefinedTypeFunctionError& rhs) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct ReservedIdentifier
|
||||||
|
{
|
||||||
|
std::string name;
|
||||||
|
|
||||||
|
bool operator==(const ReservedIdentifier& rhs) const;
|
||||||
|
};
|
||||||
|
|
||||||
using TypeErrorData = Variant<
|
using TypeErrorData = Variant<
|
||||||
TypeMismatch,
|
TypeMismatch,
|
||||||
UnknownSymbol,
|
UnknownSymbol,
|
||||||
|
@ -492,7 +499,7 @@ using TypeErrorData = Variant<
|
||||||
TypesAreUnrelated,
|
TypesAreUnrelated,
|
||||||
NormalizationTooComplex,
|
NormalizationTooComplex,
|
||||||
TypePackMismatch,
|
TypePackMismatch,
|
||||||
DynamicPropertyLookupOnClassesUnsafe,
|
DynamicPropertyLookupOnExternTypesUnsafe,
|
||||||
UninhabitedTypeFunction,
|
UninhabitedTypeFunction,
|
||||||
UninhabitedTypePackFunction,
|
UninhabitedTypePackFunction,
|
||||||
WhereClauseNeeded,
|
WhereClauseNeeded,
|
||||||
|
@ -504,7 +511,8 @@ using TypeErrorData = Variant<
|
||||||
UnexpectedTypeInSubtyping,
|
UnexpectedTypeInSubtyping,
|
||||||
UnexpectedTypePackInSubtyping,
|
UnexpectedTypePackInSubtyping,
|
||||||
ExplicitFunctionAnnotationRecommended,
|
ExplicitFunctionAnnotationRecommended,
|
||||||
UserDefinedTypeFunctionError>;
|
UserDefinedTypeFunctionError,
|
||||||
|
ReservedIdentifier>;
|
||||||
|
|
||||||
struct TypeErrorSummary
|
struct TypeErrorSummary
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <string>
|
#include <memory>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
|
@ -19,7 +20,7 @@ struct SourceCode
|
||||||
None,
|
None,
|
||||||
Module,
|
Module,
|
||||||
Script,
|
Script,
|
||||||
Local
|
Local_DEPRECATED
|
||||||
};
|
};
|
||||||
|
|
||||||
std::string source;
|
std::string source;
|
||||||
|
@ -32,15 +33,71 @@ struct ModuleInfo
|
||||||
bool optional = false;
|
bool optional = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct RequireAlias
|
||||||
|
{
|
||||||
|
std::string alias; // Unprefixed alias name (no leading `@`).
|
||||||
|
std::vector<std::string> tags = {};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RequireNode
|
||||||
|
{
|
||||||
|
virtual ~RequireNode() {}
|
||||||
|
|
||||||
|
// Get the path component representing this node.
|
||||||
|
virtual std::string getPathComponent() const = 0;
|
||||||
|
|
||||||
|
// Get the displayed user-facing label for this node, defaults to getPathComponent()
|
||||||
|
virtual std::string getLabel() const
|
||||||
|
{
|
||||||
|
return getPathComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get tags to attach to this node's RequireSuggestion (defaults to none).
|
||||||
|
virtual std::vector<std::string> getTags() const
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: resolvePathToNode() can ultimately be replaced with a call into
|
||||||
|
// require-by-string's path resolution algorithm. This will first require
|
||||||
|
// generalizing that algorithm to work with a virtual file system.
|
||||||
|
virtual std::unique_ptr<RequireNode> resolvePathToNode(const std::string& path) const = 0;
|
||||||
|
|
||||||
|
// Get children of this node, if any (if this node represents a directory).
|
||||||
|
virtual std::vector<std::unique_ptr<RequireNode>> getChildren() const = 0;
|
||||||
|
|
||||||
|
// A list of the aliases available to this node.
|
||||||
|
virtual std::vector<RequireAlias> getAvailableAliases() const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
struct RequireSuggestion
|
struct RequireSuggestion
|
||||||
{
|
{
|
||||||
std::string label;
|
std::string label;
|
||||||
std::string fullPath;
|
std::string fullPath;
|
||||||
|
std::vector<std::string> tags;
|
||||||
};
|
};
|
||||||
using RequireSuggestions = std::vector<RequireSuggestion>;
|
using RequireSuggestions = std::vector<RequireSuggestion>;
|
||||||
|
|
||||||
|
struct RequireSuggester
|
||||||
|
{
|
||||||
|
virtual ~RequireSuggester() {}
|
||||||
|
std::optional<RequireSuggestions> getRequireSuggestions(const ModuleName& requirer, const std::optional<std::string>& pathString) const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual std::unique_ptr<RequireNode> getNode(const ModuleName& name) const = 0;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::optional<RequireSuggestions> getRequireSuggestionsImpl(const ModuleName& requirer, const std::optional<std::string>& path) const;
|
||||||
|
};
|
||||||
|
|
||||||
struct FileResolver
|
struct FileResolver
|
||||||
{
|
{
|
||||||
|
FileResolver() = default;
|
||||||
|
FileResolver(std::shared_ptr<RequireSuggester> requireSuggester)
|
||||||
|
: requireSuggester(std::move(requireSuggester))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
virtual ~FileResolver() {}
|
virtual ~FileResolver() {}
|
||||||
|
|
||||||
virtual std::optional<SourceCode> readSource(const ModuleName& name) = 0;
|
virtual std::optional<SourceCode> readSource(const ModuleName& name) = 0;
|
||||||
|
@ -60,10 +117,9 @@ struct FileResolver
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual std::optional<RequireSuggestions> getRequireSuggestions(const ModuleName& requirer, const std::optional<std::string>& pathString) const
|
std::optional<RequireSuggestions> getRequireSuggestions(const ModuleName& requirer, const std::optional<std::string>& pathString) const;
|
||||||
{
|
|
||||||
return std::nullopt;
|
std::shared_ptr<RequireSuggester> requireSuggester;
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct NullFileResolver : FileResolver
|
struct NullFileResolver : FileResolver
|
||||||
|
|
|
@ -15,12 +15,42 @@ namespace Luau
|
||||||
{
|
{
|
||||||
struct FrontendOptions;
|
struct FrontendOptions;
|
||||||
|
|
||||||
|
enum class FragmentAutocompleteWaypoint
|
||||||
|
{
|
||||||
|
ParseFragmentEnd,
|
||||||
|
CloneModuleStart,
|
||||||
|
CloneModuleEnd,
|
||||||
|
DfgBuildEnd,
|
||||||
|
CloneAndSquashScopeStart,
|
||||||
|
CloneAndSquashScopeEnd,
|
||||||
|
ConstraintSolverStart,
|
||||||
|
ConstraintSolverEnd,
|
||||||
|
TypecheckFragmentEnd,
|
||||||
|
AutocompleteEnd,
|
||||||
|
COUNT,
|
||||||
|
};
|
||||||
|
|
||||||
|
class IFragmentAutocompleteReporter
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual void reportWaypoint(FragmentAutocompleteWaypoint) = 0;
|
||||||
|
virtual void reportFragmentString(std::string_view) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class FragmentTypeCheckStatus
|
||||||
|
{
|
||||||
|
SkipAutocomplete,
|
||||||
|
Success,
|
||||||
|
};
|
||||||
|
|
||||||
struct FragmentAutocompleteAncestryResult
|
struct FragmentAutocompleteAncestryResult
|
||||||
{
|
{
|
||||||
DenseHashMap<AstName, AstLocal*> localMap{AstName()};
|
DenseHashMap<AstName, AstLocal*> localMap{AstName()};
|
||||||
std::vector<AstLocal*> localStack;
|
std::vector<AstLocal*> localStack;
|
||||||
std::vector<AstNode*> ancestry;
|
std::vector<AstNode*> ancestry;
|
||||||
AstStat* nearestStatement = nullptr;
|
AstStat* nearestStatement = nullptr;
|
||||||
|
AstStatBlock* parentBlock = nullptr;
|
||||||
|
Location fragmentSelectionRegion;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct FragmentParseResult
|
struct FragmentParseResult
|
||||||
|
@ -29,7 +59,9 @@ struct FragmentParseResult
|
||||||
AstStatBlock* root = nullptr;
|
AstStatBlock* root = nullptr;
|
||||||
std::vector<AstNode*> ancestry;
|
std::vector<AstNode*> ancestry;
|
||||||
AstStat* nearestStatement = nullptr;
|
AstStat* nearestStatement = nullptr;
|
||||||
|
std::vector<Comment> commentLocations;
|
||||||
std::unique_ptr<Allocator> alloc = std::make_unique<Allocator>();
|
std::unique_ptr<Allocator> alloc = std::make_unique<Allocator>();
|
||||||
|
Position scopePos{0, 0};
|
||||||
};
|
};
|
||||||
|
|
||||||
struct FragmentTypeCheckResult
|
struct FragmentTypeCheckResult
|
||||||
|
@ -43,20 +75,48 @@ struct FragmentAutocompleteResult
|
||||||
{
|
{
|
||||||
ModulePtr incrementalModule;
|
ModulePtr incrementalModule;
|
||||||
Scope* freshScope;
|
Scope* freshScope;
|
||||||
TypeArena arenaForAutocomplete;
|
TypeArena arenaForAutocomplete_DEPRECATED;
|
||||||
AutocompleteResult acResults;
|
AutocompleteResult acResults;
|
||||||
};
|
};
|
||||||
|
|
||||||
FragmentAutocompleteAncestryResult findAncestryForFragmentParse(AstStatBlock* root, const Position& cursorPos);
|
struct FragmentRegion
|
||||||
|
{
|
||||||
|
Location fragmentLocation;
|
||||||
|
AstStat* nearestStatement = nullptr; // used for tests
|
||||||
|
AstStatBlock* parentBlock = nullptr; // used for scope detection
|
||||||
|
};
|
||||||
|
|
||||||
FragmentParseResult parseFragment(const SourceModule& srcModule, std::string_view src, const Position& cursorPos);
|
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);
|
||||||
|
|
||||||
FragmentTypeCheckResult typecheckFragment(
|
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,
|
Frontend& frontend,
|
||||||
const ModuleName& moduleName,
|
const ModuleName& moduleName,
|
||||||
const Position& cursorPos,
|
const Position& cursorPos,
|
||||||
std::optional<FrontendOptions> opts,
|
std::optional<FrontendOptions> opts,
|
||||||
std::string_view src
|
std::string_view src,
|
||||||
|
std::optional<Position> fragmentEndPosition,
|
||||||
|
AstStatBlock* recentParse = nullptr,
|
||||||
|
IFragmentAutocompleteReporter* reporter = nullptr
|
||||||
);
|
);
|
||||||
|
|
||||||
FragmentAutocompleteResult fragmentAutocomplete(
|
FragmentAutocompleteResult fragmentAutocomplete(
|
||||||
|
@ -65,8 +125,72 @@ FragmentAutocompleteResult fragmentAutocomplete(
|
||||||
const ModuleName& moduleName,
|
const ModuleName& moduleName,
|
||||||
Position cursorPosition,
|
Position cursorPosition,
|
||||||
std::optional<FrontendOptions> opts,
|
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
|
} // namespace Luau
|
||||||
|
|
|
@ -7,9 +7,9 @@
|
||||||
#include "Luau/ModuleResolver.h"
|
#include "Luau/ModuleResolver.h"
|
||||||
#include "Luau/RequireTracer.h"
|
#include "Luau/RequireTracer.h"
|
||||||
#include "Luau/Scope.h"
|
#include "Luau/Scope.h"
|
||||||
|
#include "Luau/Set.h"
|
||||||
#include "Luau/TypeCheckLimits.h"
|
#include "Luau/TypeCheckLimits.h"
|
||||||
#include "Luau/Variant.h"
|
#include "Luau/Variant.h"
|
||||||
#include "Luau/AnyTypeSummary.h"
|
|
||||||
|
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
@ -31,8 +31,8 @@ struct ModuleResolver;
|
||||||
struct ParseResult;
|
struct ParseResult;
|
||||||
struct HotComment;
|
struct HotComment;
|
||||||
struct BuildQueueItem;
|
struct BuildQueueItem;
|
||||||
|
struct BuildQueueWorkState;
|
||||||
struct FrontendCancellationToken;
|
struct FrontendCancellationToken;
|
||||||
struct AnyTypeSummary;
|
|
||||||
|
|
||||||
struct LoadDefinitionFileResult
|
struct LoadDefinitionFileResult
|
||||||
{
|
{
|
||||||
|
@ -56,13 +56,32 @@ struct SourceNode
|
||||||
return forAutocomplete ? dirtyModuleForAutocomplete : dirtyModule;
|
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;
|
ModuleName name;
|
||||||
std::string humanReadableName;
|
std::string humanReadableName;
|
||||||
DenseHashSet<ModuleName> requireSet{{}};
|
DenseHashSet<ModuleName> requireSet{{}};
|
||||||
std::vector<std::pair<ModuleName, Location>> requireLocations;
|
std::vector<std::pair<ModuleName, Location>> requireLocations;
|
||||||
|
Set<ModuleName> dependents{{}};
|
||||||
|
|
||||||
bool dirtySourceModule = true;
|
bool dirtySourceModule = true;
|
||||||
bool dirtyModule = true;
|
bool dirtyModule = true;
|
||||||
bool dirtyModuleForAutocomplete = true;
|
bool dirtyModuleForAutocomplete = true;
|
||||||
|
|
||||||
|
bool invalidModuleDependency = true;
|
||||||
|
bool invalidModuleDependencyForAutocomplete = true;
|
||||||
|
|
||||||
double autocompleteLimitsMult = 1.0;
|
double autocompleteLimitsMult = 1.0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -117,7 +136,7 @@ struct FrontendModuleResolver : ModuleResolver
|
||||||
std::optional<ModuleInfo> resolveModuleInfo(const ModuleName& currentModuleName, const AstExpr& pathExpr) override;
|
std::optional<ModuleInfo> resolveModuleInfo(const ModuleName& currentModuleName, const AstExpr& pathExpr) override;
|
||||||
std::string getHumanReadableModuleName(const ModuleName& moduleName) const 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();
|
void clearModules();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -151,9 +170,13 @@ struct Frontend
|
||||||
// Parse and typecheck module graph
|
// Parse and typecheck module graph
|
||||||
CheckResult check(const ModuleName& name, std::optional<FrontendOptions> optionOverride = {}); // new shininess
|
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;
|
bool isDirty(const ModuleName& name, bool forAutocomplete = false) const;
|
||||||
void markDirty(const ModuleName& name, std::vector<ModuleName>* markedDirty = nullptr);
|
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.
|
/** Borrow a pointer into the SourceModule cache.
|
||||||
*
|
*
|
||||||
* Returns nullptr if we don't have it. This could mean that the script
|
* 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::optional<CheckResult> getCheckResult(const ModuleName& name, bool accumulateNested, bool forAutocomplete = false);
|
||||||
|
std::vector<ModuleName> getRequiredScripts(const ModuleName& name);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ModulePtr check(
|
ModulePtr check(
|
||||||
|
@ -226,6 +250,9 @@ private:
|
||||||
void checkBuildQueueItem(BuildQueueItem& item);
|
void checkBuildQueueItem(BuildQueueItem& item);
|
||||||
void checkBuildQueueItems(std::vector<BuildQueueItem>& items);
|
void checkBuildQueueItems(std::vector<BuildQueueItem>& items);
|
||||||
void recordItemResult(const BuildQueueItem& item);
|
void recordItemResult(const BuildQueueItem& item);
|
||||||
|
void performQueueItemTask(std::shared_ptr<BuildQueueWorkState> state, size_t itemPos);
|
||||||
|
void sendQueueItemTask(std::shared_ptr<BuildQueueWorkState> state, size_t itemPos);
|
||||||
|
void sendQueueCycleItemTask(std::shared_ptr<BuildQueueWorkState> state);
|
||||||
|
|
||||||
static LintResult classifyLints(const std::vector<LintWarning>& warnings, const Config& config);
|
static LintResult classifyLints(const std::vector<LintWarning>& warnings, const Config& config);
|
||||||
|
|
||||||
|
@ -271,6 +298,7 @@ ModulePtr check(
|
||||||
NotNull<ModuleResolver> moduleResolver,
|
NotNull<ModuleResolver> moduleResolver,
|
||||||
NotNull<FileResolver> fileResolver,
|
NotNull<FileResolver> fileResolver,
|
||||||
const ScopePtr& globalScope,
|
const ScopePtr& globalScope,
|
||||||
|
const ScopePtr& typeFunctionScope,
|
||||||
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope,
|
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope,
|
||||||
FrontendOptions options,
|
FrontendOptions options,
|
||||||
TypeCheckLimits limits
|
TypeCheckLimits limits
|
||||||
|
@ -285,6 +313,7 @@ ModulePtr check(
|
||||||
NotNull<ModuleResolver> moduleResolver,
|
NotNull<ModuleResolver> moduleResolver,
|
||||||
NotNull<FileResolver> fileResolver,
|
NotNull<FileResolver> fileResolver,
|
||||||
const ScopePtr& globalScope,
|
const ScopePtr& globalScope,
|
||||||
|
const ScopePtr& typeFunctionScope,
|
||||||
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope,
|
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope,
|
||||||
FrontendOptions options,
|
FrontendOptions options,
|
||||||
TypeCheckLimits limits,
|
TypeCheckLimits limits,
|
||||||
|
|
|
@ -8,12 +8,75 @@
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
|
||||||
|
template<typename TID>
|
||||||
|
struct GeneralizationParams
|
||||||
|
{
|
||||||
|
bool foundOutsideFunctions = false;
|
||||||
|
size_t useCount = 0;
|
||||||
|
Polarity polarity = Polarity::None;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename TID>
|
||||||
|
struct GeneralizationResult
|
||||||
|
{
|
||||||
|
std::optional<TID> result;
|
||||||
|
|
||||||
|
// True if the provided type was replaced with a generic.
|
||||||
|
bool wasReplacedByGeneric = false;
|
||||||
|
|
||||||
|
bool resourceLimitsExceeded = false;
|
||||||
|
|
||||||
|
explicit operator bool() const
|
||||||
|
{
|
||||||
|
return bool(result);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Replace a single free type by its bounds according to the polarity provided.
|
||||||
|
GeneralizationResult<TypeId> generalizeType(
|
||||||
|
NotNull<TypeArena> arena,
|
||||||
|
NotNull<BuiltinTypes> builtinTypes,
|
||||||
|
NotNull<Scope> scope,
|
||||||
|
TypeId freeTy,
|
||||||
|
const GeneralizationParams<TypeId>& params
|
||||||
|
);
|
||||||
|
|
||||||
|
// Generalize one type pack
|
||||||
|
GeneralizationResult<TypePackId> generalizeTypePack(
|
||||||
|
NotNull<TypeArena> arena,
|
||||||
|
NotNull<BuiltinTypes> builtinTypes,
|
||||||
|
NotNull<Scope> scope,
|
||||||
|
TypePackId tp,
|
||||||
|
const GeneralizationParams<TypePackId>& params
|
||||||
|
);
|
||||||
|
|
||||||
|
void sealTable(NotNull<Scope> scope, TypeId ty);
|
||||||
|
|
||||||
|
/** Attempt to generalize a type.
|
||||||
|
*
|
||||||
|
* If generalizationTarget is set, then only that type will be replaced by its
|
||||||
|
* bounds. The way this is intended to be used is that ty is some function that
|
||||||
|
* is not fully generalized, and generalizationTarget is a type within its
|
||||||
|
* signature. There should be no further constraints that could affect the
|
||||||
|
* bounds of generalizationTarget.
|
||||||
|
*
|
||||||
|
* Returns nullopt if generalization failed due to resources limits.
|
||||||
|
*/
|
||||||
std::optional<TypeId> generalize(
|
std::optional<TypeId> generalize(
|
||||||
NotNull<TypeArena> arena,
|
NotNull<TypeArena> arena,
|
||||||
NotNull<BuiltinTypes> builtinTypes,
|
NotNull<BuiltinTypes> builtinTypes,
|
||||||
NotNull<Scope> scope,
|
NotNull<Scope> scope,
|
||||||
NotNull<DenseHashSet<TypeId>> bakedTypes,
|
NotNull<DenseHashSet<TypeId>> cachedTypes,
|
||||||
TypeId ty,
|
TypeId ty,
|
||||||
/* avoid sealing tables*/ bool avoidSealingTables = false
|
std::optional<TypeId> generalizationTarget = {}
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
void pruneUnnecessaryGenerics(
|
||||||
|
NotNull<TypeArena> arena,
|
||||||
|
NotNull<BuiltinTypes> builtinTypes,
|
||||||
|
NotNull<Scope> scope,
|
||||||
|
NotNull<DenseHashSet<TypeId>> cachedTypes,
|
||||||
|
TypeId ty
|
||||||
|
);
|
||||||
|
|
||||||
|
} // namespace Luau
|
||||||
|
|
|
@ -19,7 +19,9 @@ struct GlobalTypes
|
||||||
|
|
||||||
TypeArena globalTypes;
|
TypeArena globalTypes;
|
||||||
SourceModule globalNames; // names for symbols entered into globalScope
|
SourceModule globalNames; // names for symbols entered into globalScope
|
||||||
|
|
||||||
ScopePtr globalScope; // shared by all modules
|
ScopePtr globalScope; // shared by all modules
|
||||||
|
ScopePtr globalTypeFunctionScope; // shared by all modules
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Luau
|
} // namespace Luau
|
||||||
|
|
16
Analysis/include/Luau/InferPolarity.h
Normal file
16
Analysis/include/Luau/InferPolarity.h
Normal 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
|
|
@ -67,6 +67,19 @@ public:
|
||||||
return &pairs.at(it->second).second;
|
return &pairs.at(it->second).second;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
V& operator[](const K& k)
|
||||||
|
{
|
||||||
|
auto it = indices.find(k);
|
||||||
|
if (it == indices.end())
|
||||||
|
{
|
||||||
|
pairs.push_back(std::make_pair(k, V()));
|
||||||
|
indices[k] = pairs.size() - 1;
|
||||||
|
return pairs.back().second;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return pairs.at(it->second).second;
|
||||||
|
}
|
||||||
|
|
||||||
const_iterator begin() const
|
const_iterator begin() const
|
||||||
{
|
{
|
||||||
return pairs.begin();
|
return pairs.begin();
|
||||||
|
|
|
@ -133,9 +133,9 @@ struct GenericTypeFinder : TypeOnceVisitor
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool visit(TypeId ty, const Luau::ClassType&) override
|
bool visit(TypeId ty, const Luau::ExternType&) override
|
||||||
{
|
{
|
||||||
// During function instantiation, classes are not traversed even if they have generics
|
// During function instantiation, extern types are not traversed even if they have generics
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -53,7 +53,7 @@ struct Replacer : Substitution
|
||||||
};
|
};
|
||||||
|
|
||||||
// A substitution which replaces generic functions by monomorphic functions
|
// 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.
|
// Mapping from generic types to free types to be used in instantiation.
|
||||||
DenseHashMap<TypeId, TypeId> genericSubstitutions{nullptr};
|
DenseHashMap<TypeId, TypeId> genericSubstitutions{nullptr};
|
||||||
|
|
|
@ -8,7 +8,6 @@
|
||||||
#include "Luau/ParseResult.h"
|
#include "Luau/ParseResult.h"
|
||||||
#include "Luau/Scope.h"
|
#include "Luau/Scope.h"
|
||||||
#include "Luau/TypeArena.h"
|
#include "Luau/TypeArena.h"
|
||||||
#include "Luau/AnyTypeSummary.h"
|
|
||||||
#include "Luau/DataFlowGraph.h"
|
#include "Luau/DataFlowGraph.h"
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
@ -19,8 +18,13 @@
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
|
||||||
|
using LogLuauProc = void (*)(std::string_view, std::string_view);
|
||||||
|
extern LogLuauProc logLuau;
|
||||||
|
|
||||||
|
void setLogLuau(LogLuauProc ll);
|
||||||
|
void resetLogLuauProc();
|
||||||
|
|
||||||
struct Module;
|
struct Module;
|
||||||
struct AnyTypeSummary;
|
|
||||||
|
|
||||||
using ScopePtr = std::shared_ptr<struct Scope>;
|
using ScopePtr = std::shared_ptr<struct Scope>;
|
||||||
using ModulePtr = std::shared_ptr<Module>;
|
using ModulePtr = std::shared_ptr<Module>;
|
||||||
|
@ -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 SourceModule& sourceModule, Position pos);
|
||||||
bool isWithinComment(const ParseResult& result, Position pos);
|
bool isWithinComment(const ParseResult& result, Position pos);
|
||||||
|
|
||||||
|
@ -68,19 +73,19 @@ struct Module
|
||||||
{
|
{
|
||||||
~Module();
|
~Module();
|
||||||
|
|
||||||
|
// TODO: Clip this when we clip FFlagLuauSolverV2
|
||||||
|
bool checkedInNewSolver = false;
|
||||||
|
|
||||||
ModuleName name;
|
ModuleName name;
|
||||||
std::string humanReadableName;
|
std::string humanReadableName;
|
||||||
|
|
||||||
TypeArena interfaceTypes;
|
TypeArena interfaceTypes;
|
||||||
TypeArena internalTypes;
|
TypeArena internalTypes;
|
||||||
|
|
||||||
// Summary of Ast Nodes that either contain
|
|
||||||
// user annotated anys or typechecker inferred anys
|
|
||||||
AnyTypeSummary ats{};
|
|
||||||
|
|
||||||
// Scopes and AST types refer to parse data, so we need to keep that alive
|
// Scopes and AST types refer to parse data, so we need to keep that alive
|
||||||
std::shared_ptr<Allocator> allocator;
|
std::shared_ptr<Allocator> allocator;
|
||||||
std::shared_ptr<AstNameTable> names;
|
std::shared_ptr<AstNameTable> names;
|
||||||
|
AstStatBlock* root = nullptr;
|
||||||
|
|
||||||
std::vector<std::pair<Location, ScopePtr>> scopes; // never empty
|
std::vector<std::pair<Location, ScopePtr>> scopes; // never empty
|
||||||
|
|
||||||
|
@ -132,9 +137,11 @@ struct Module
|
||||||
|
|
||||||
TypePackId returnType = nullptr;
|
TypePackId returnType = nullptr;
|
||||||
std::unordered_map<Name, TypeFun> exportedTypeBindings;
|
std::unordered_map<Name, TypeFun> exportedTypeBindings;
|
||||||
// We also need to keep DFG data alive between runs
|
|
||||||
std::shared_ptr<DataFlowGraph> dataFlowGraph = nullptr;
|
// Arenas related to the DFG must persist after the DFG no longer exists, as
|
||||||
std::vector<std::unique_ptr<DfgScope>> dfgScopes;
|
// Module objects maintain raw pointers to objects in these arenas.
|
||||||
|
DefArena defArena;
|
||||||
|
RefinementKeyArena keyArena;
|
||||||
|
|
||||||
bool hasModuleScope() const;
|
bool hasModuleScope() const;
|
||||||
ScopePtr getModuleScope() const;
|
ScopePtr getModuleScope() const;
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "Luau/DataFlowGraph.h"
|
||||||
|
#include "Luau/EqSatSimplification.h"
|
||||||
#include "Luau/Module.h"
|
#include "Luau/Module.h"
|
||||||
#include "Luau/NotNull.h"
|
#include "Luau/NotNull.h"
|
||||||
#include "Luau/DataFlowGraph.h"
|
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
@ -15,6 +16,7 @@ struct TypeCheckLimits;
|
||||||
|
|
||||||
void checkNonStrict(
|
void checkNonStrict(
|
||||||
NotNull<BuiltinTypes> builtinTypes,
|
NotNull<BuiltinTypes> builtinTypes,
|
||||||
|
NotNull<Simplifier> simplifier,
|
||||||
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
|
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
|
||||||
NotNull<InternalErrorReporter> ice,
|
NotNull<InternalErrorReporter> ice,
|
||||||
NotNull<UnifierSharedState> unifierState,
|
NotNull<UnifierSharedState> unifierState,
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "Luau/EqSatSimplification.h"
|
||||||
#include "Luau/NotNull.h"
|
#include "Luau/NotNull.h"
|
||||||
#include "Luau/Set.h"
|
#include "Luau/Set.h"
|
||||||
#include "Luau/TypeFwd.h"
|
#include "Luau/TypeFwd.h"
|
||||||
|
@ -21,8 +22,22 @@ struct Scope;
|
||||||
|
|
||||||
using ModulePtr = std::shared_ptr<Module>;
|
using ModulePtr = std::shared_ptr<Module>;
|
||||||
|
|
||||||
bool isSubtype(TypeId subTy, TypeId superTy, NotNull<Scope> scope, NotNull<BuiltinTypes> builtinTypes, InternalErrorReporter& ice);
|
bool isSubtype(
|
||||||
bool isSubtype(TypePackId subTy, TypePackId superTy, NotNull<Scope> scope, NotNull<BuiltinTypes> builtinTypes, InternalErrorReporter& ice);
|
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
|
class TypeIds
|
||||||
{
|
{
|
||||||
|
@ -166,7 +181,7 @@ struct NormalizedStringType
|
||||||
|
|
||||||
bool isSubtype(const NormalizedStringType& subStr, const NormalizedStringType& superStr);
|
bool isSubtype(const NormalizedStringType& subStr, const NormalizedStringType& superStr);
|
||||||
|
|
||||||
struct NormalizedClassType
|
struct NormalizedExternType
|
||||||
{
|
{
|
||||||
/** Has the following structure:
|
/** Has the following structure:
|
||||||
*
|
*
|
||||||
|
@ -177,7 +192,7 @@ struct NormalizedClassType
|
||||||
*
|
*
|
||||||
* Each TypeId is a class type.
|
* Each TypeId is a class type.
|
||||||
*/
|
*/
|
||||||
std::unordered_map<TypeId, TypeIds> classes;
|
std::unordered_map<TypeId, TypeIds> externTypes;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* In order to maintain a consistent insertion order, we use this vector to
|
* In order to maintain a consistent insertion order, we use this vector to
|
||||||
|
@ -230,7 +245,7 @@ enum class NormalizationResult
|
||||||
};
|
};
|
||||||
|
|
||||||
// A normalized type is either any, unknown, or one of the form P | T | F | G where
|
// A normalized type is either any, unknown, or one of the form P | T | F | G where
|
||||||
// * P is a union of primitive types (including singletons, classes and the error type)
|
// * P is a union of primitive types (including singletons, extern types and the error type)
|
||||||
// * T is a union of table types
|
// * T is a union of table types
|
||||||
// * F is a union of an intersection of function types
|
// * F is a union of an intersection of function types
|
||||||
// * G is a union of generic/free/blocked types, intersected with a normalized type
|
// * G is a union of generic/free/blocked types, intersected with a normalized type
|
||||||
|
@ -245,7 +260,7 @@ struct NormalizedType
|
||||||
// This type is either never, boolean type, or a boolean singleton.
|
// This type is either never, boolean type, or a boolean singleton.
|
||||||
TypeId booleans;
|
TypeId booleans;
|
||||||
|
|
||||||
NormalizedClassType classes;
|
NormalizedExternType externTypes;
|
||||||
|
|
||||||
// The error part of the type.
|
// The error part of the type.
|
||||||
// This type is either never or the error type.
|
// This type is either never or the error type.
|
||||||
|
@ -318,7 +333,7 @@ struct NormalizedType
|
||||||
// Helpers that improve readability of the above (they just say if the component is present)
|
// Helpers that improve readability of the above (they just say if the component is present)
|
||||||
bool hasTops() const;
|
bool hasTops() const;
|
||||||
bool hasBooleans() const;
|
bool hasBooleans() const;
|
||||||
bool hasClasses() const;
|
bool hasExternTypes() const;
|
||||||
bool hasErrors() const;
|
bool hasErrors() const;
|
||||||
bool hasNils() const;
|
bool hasNils() const;
|
||||||
bool hasNumbers() const;
|
bool hasNumbers() const;
|
||||||
|
@ -376,10 +391,10 @@ public:
|
||||||
void unionTysWithTy(TypeIds& here, TypeId there);
|
void unionTysWithTy(TypeIds& here, TypeId there);
|
||||||
TypeId unionOfTops(TypeId here, TypeId there);
|
TypeId unionOfTops(TypeId here, TypeId there);
|
||||||
TypeId unionOfBools(TypeId here, TypeId there);
|
TypeId unionOfBools(TypeId here, TypeId there);
|
||||||
void unionClassesWithClass(TypeIds& heres, TypeId there);
|
void unionExternTypesWithExternType(TypeIds& heres, TypeId there);
|
||||||
void unionClasses(TypeIds& heres, const TypeIds& theres);
|
void unionExternTypes(TypeIds& heres, const TypeIds& theres);
|
||||||
void unionClassesWithClass(NormalizedClassType& heres, TypeId there);
|
void unionExternTypesWithExternType(NormalizedExternType& heres, TypeId there);
|
||||||
void unionClasses(NormalizedClassType& heres, const NormalizedClassType& theres);
|
void unionExternTypes(NormalizedExternType& heres, const NormalizedExternType& theres);
|
||||||
void unionStrings(NormalizedStringType& here, const NormalizedStringType& there);
|
void unionStrings(NormalizedStringType& here, const NormalizedStringType& there);
|
||||||
std::optional<TypePackId> unionOfTypePacks(TypePackId here, TypePackId there);
|
std::optional<TypePackId> unionOfTypePacks(TypePackId here, TypePackId there);
|
||||||
std::optional<TypeId> unionOfFunctions(TypeId here, TypeId there);
|
std::optional<TypeId> unionOfFunctions(TypeId here, TypeId there);
|
||||||
|
@ -408,8 +423,8 @@ public:
|
||||||
// ------- Normalizing intersections
|
// ------- Normalizing intersections
|
||||||
TypeId intersectionOfTops(TypeId here, TypeId there);
|
TypeId intersectionOfTops(TypeId here, TypeId there);
|
||||||
TypeId intersectionOfBools(TypeId here, TypeId there);
|
TypeId intersectionOfBools(TypeId here, TypeId there);
|
||||||
void intersectClasses(NormalizedClassType& heres, const NormalizedClassType& theres);
|
void intersectExternTypes(NormalizedExternType& heres, const NormalizedExternType& theres);
|
||||||
void intersectClassesWithClass(NormalizedClassType& heres, TypeId there);
|
void intersectExternTypesWithExternType(NormalizedExternType& heres, TypeId there);
|
||||||
void intersectStrings(NormalizedStringType& here, const NormalizedStringType& there);
|
void intersectStrings(NormalizedStringType& here, const NormalizedStringType& there);
|
||||||
std::optional<TypePackId> intersectionOfTypePacks(TypePackId here, TypePackId there);
|
std::optional<TypePackId> intersectionOfTypePacks(TypePackId here, TypePackId there);
|
||||||
std::optional<TypeId> intersectionOfTables(TypeId here, TypeId there, SeenTablePropPairs& seenTablePropPairs, Set<TypeId>& seenSet);
|
std::optional<TypeId> intersectionOfTables(TypeId here, TypeId there, SeenTablePropPairs& seenTablePropPairs, Set<TypeId>& seenSet);
|
||||||
|
|
|
@ -2,12 +2,13 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Luau/Ast.h"
|
#include "Luau/Ast.h"
|
||||||
#include "Luau/InsertionOrderedMap.h"
|
#include "Luau/EqSatSimplification.h"
|
||||||
#include "Luau/NotNull.h"
|
|
||||||
#include "Luau/TypeFwd.h"
|
|
||||||
#include "Luau/Location.h"
|
|
||||||
#include "Luau/Error.h"
|
#include "Luau/Error.h"
|
||||||
|
#include "Luau/InsertionOrderedMap.h"
|
||||||
|
#include "Luau/Location.h"
|
||||||
|
#include "Luau/NotNull.h"
|
||||||
#include "Luau/Subtyping.h"
|
#include "Luau/Subtyping.h"
|
||||||
|
#include "Luau/TypeFwd.h"
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
@ -34,6 +35,7 @@ struct OverloadResolver
|
||||||
OverloadResolver(
|
OverloadResolver(
|
||||||
NotNull<BuiltinTypes> builtinTypes,
|
NotNull<BuiltinTypes> builtinTypes,
|
||||||
NotNull<TypeArena> arena,
|
NotNull<TypeArena> arena,
|
||||||
|
NotNull<Simplifier> simplifier,
|
||||||
NotNull<Normalizer> normalizer,
|
NotNull<Normalizer> normalizer,
|
||||||
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
|
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
|
||||||
NotNull<Scope> scope,
|
NotNull<Scope> scope,
|
||||||
|
@ -44,6 +46,7 @@ struct OverloadResolver
|
||||||
|
|
||||||
NotNull<BuiltinTypes> builtinTypes;
|
NotNull<BuiltinTypes> builtinTypes;
|
||||||
NotNull<TypeArena> arena;
|
NotNull<TypeArena> arena;
|
||||||
|
NotNull<Simplifier> simplifier;
|
||||||
NotNull<Normalizer> normalizer;
|
NotNull<Normalizer> normalizer;
|
||||||
NotNull<TypeFunctionRuntime> typeFunctionRuntime;
|
NotNull<TypeFunctionRuntime> typeFunctionRuntime;
|
||||||
NotNull<Scope> scope;
|
NotNull<Scope> scope;
|
||||||
|
@ -110,6 +113,7 @@ struct SolveResult
|
||||||
SolveResult solveFunctionCall(
|
SolveResult solveFunctionCall(
|
||||||
NotNull<TypeArena> arena,
|
NotNull<TypeArena> arena,
|
||||||
NotNull<BuiltinTypes> builtinTypes,
|
NotNull<BuiltinTypes> builtinTypes,
|
||||||
|
NotNull<Simplifier> simplifier,
|
||||||
NotNull<Normalizer> normalizer,
|
NotNull<Normalizer> normalizer,
|
||||||
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
|
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
|
||||||
NotNull<InternalErrorReporter> iceReporter,
|
NotNull<InternalErrorReporter> iceReporter,
|
||||||
|
|
68
Analysis/include/Luau/Polarity.h
Normal file
68
Analysis/include/Luau/Polarity.h
Normal 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
|
|
@ -16,7 +16,7 @@ struct Scope;
|
||||||
|
|
||||||
void quantify(TypeId ty, TypeLevel level);
|
void quantify(TypeId ty, TypeLevel level);
|
||||||
|
|
||||||
// TODO: This is eerily similar to the pattern that NormalizedClassType
|
// TODO: This is eerily similar to the pattern that NormalizedExternType
|
||||||
// implements. We could, and perhaps should, merge them together.
|
// implements. We could, and perhaps should, merge them together.
|
||||||
template<typename K, typename V>
|
template<typename K, typename V>
|
||||||
struct OrderedMap
|
struct OrderedMap
|
||||||
|
@ -31,13 +31,4 @@ struct OrderedMap
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct QuantifierResult
|
|
||||||
{
|
|
||||||
TypeId result;
|
|
||||||
OrderedMap<TypeId, TypeId> insertedGenerics;
|
|
||||||
OrderedMap<TypePackId, TypePackId> insertedGenericPacks;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::optional<QuantifierResult> quantify(TypeArena* arena, TypeId ty, Scope* scope);
|
|
||||||
|
|
||||||
} // namespace Luau
|
} // namespace Luau
|
||||||
|
|
|
@ -53,6 +53,7 @@ struct Proposition
|
||||||
{
|
{
|
||||||
const RefinementKey* key;
|
const RefinementKey* key;
|
||||||
TypeId discriminantTy;
|
TypeId discriminantTy;
|
||||||
|
bool implicitFromCall;
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
|
@ -69,6 +70,7 @@ struct RefinementArena
|
||||||
RefinementId disjunction(RefinementId lhs, RefinementId rhs);
|
RefinementId disjunction(RefinementId lhs, RefinementId rhs);
|
||||||
RefinementId equivalence(RefinementId lhs, RefinementId rhs);
|
RefinementId equivalence(RefinementId lhs, RefinementId rhs);
|
||||||
RefinementId proposition(const RefinementKey* key, TypeId discriminantTy);
|
RefinementId proposition(const RefinementKey* key, TypeId discriminantTy);
|
||||||
|
RefinementId implicitProposition(const RefinementKey* key, TypeId discriminantTy);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
TypedAllocator<Refinement> allocator;
|
TypedAllocator<Refinement> allocator;
|
||||||
|
|
|
@ -11,14 +11,12 @@
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
|
||||||
class AstStat;
|
class AstNode;
|
||||||
class AstExpr;
|
|
||||||
class AstStatBlock;
|
class AstStatBlock;
|
||||||
struct AstLocal;
|
|
||||||
|
|
||||||
struct RequireTraceResult
|
struct RequireTraceResult
|
||||||
{
|
{
|
||||||
DenseHashMap<const AstExpr*, ModuleInfo> exprs{nullptr};
|
DenseHashMap<const AstNode*, ModuleInfo> exprs{nullptr};
|
||||||
|
|
||||||
std::vector<std::pair<ModuleName, Location>> requireList;
|
std::vector<std::pair<ModuleName, Location>> requireList;
|
||||||
};
|
};
|
||||||
|
|
|
@ -35,12 +35,12 @@ struct Scope
|
||||||
explicit Scope(TypePackId returnType); // root scope
|
explicit Scope(TypePackId returnType); // root scope
|
||||||
explicit Scope(const ScopePtr& parent, int subLevel = 0); // child scope. Parent must not be nullptr.
|
explicit Scope(const ScopePtr& parent, int subLevel = 0); // child scope. Parent must not be nullptr.
|
||||||
|
|
||||||
const ScopePtr parent; // null for the root
|
ScopePtr parent; // null for the root
|
||||||
|
|
||||||
// All the children of this scope.
|
// All the children of this scope.
|
||||||
std::vector<NotNull<Scope>> children;
|
std::vector<NotNull<Scope>> children;
|
||||||
std::unordered_map<Symbol, Binding> bindings;
|
std::unordered_map<Symbol, Binding> bindings;
|
||||||
TypePackId returnType;
|
TypePackId returnType = nullptr;
|
||||||
std::optional<TypePackId> varargPack;
|
std::optional<TypePackId> varargPack;
|
||||||
|
|
||||||
TypeLevel level;
|
TypeLevel level;
|
||||||
|
@ -59,6 +59,8 @@ struct Scope
|
||||||
|
|
||||||
std::optional<TypeId> lookup(Symbol sym) const;
|
std::optional<TypeId> lookup(Symbol sym) const;
|
||||||
std::optional<TypeId> lookupUnrefinedType(DefId def) const;
|
std::optional<TypeId> lookupUnrefinedType(DefId def) const;
|
||||||
|
|
||||||
|
std::optional<TypeId> lookupRValueRefinementType(DefId def) const;
|
||||||
std::optional<TypeId> lookup(DefId def) const;
|
std::optional<TypeId> lookup(DefId def) const;
|
||||||
std::optional<std::pair<TypeId, Scope*>> lookupEx(DefId def);
|
std::optional<std::pair<TypeId, Scope*>> lookupEx(DefId def);
|
||||||
std::optional<std::pair<Binding*, Scope*>> lookupEx(Symbol sym);
|
std::optional<std::pair<Binding*, Scope*>> lookupEx(Symbol sym);
|
||||||
|
@ -71,6 +73,7 @@ struct Scope
|
||||||
|
|
||||||
// WARNING: This function linearly scans for a string key of equal value! It is thus O(n**2)
|
// WARNING: This function linearly scans for a string key of equal value! It is thus O(n**2)
|
||||||
std::optional<Binding> linearSearchForBinding(const std::string& name, bool traverseScopeChain = true) const;
|
std::optional<Binding> linearSearchForBinding(const std::string& name, bool traverseScopeChain = true) const;
|
||||||
|
std::optional<std::pair<Symbol, Binding>> linearSearchForBindingPair(const std::string& name, bool traverseScopeChain) const;
|
||||||
|
|
||||||
RefinementMap refinements;
|
RefinementMap refinements;
|
||||||
|
|
||||||
|
@ -85,12 +88,19 @@ struct Scope
|
||||||
void inheritAssignments(const ScopePtr& childScope);
|
void inheritAssignments(const ScopePtr& childScope);
|
||||||
void inheritRefinements(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
|
// For mutually recursive type aliases, it's important that
|
||||||
// they use the same types for the same names.
|
// 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>}`
|
// 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.
|
// 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, TypeId> typeAliasTypeParameters;
|
||||||
std::unordered_map<Name, TypePackId> typeAliasTypePackParameters;
|
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
|
// Returns true iff the left scope encloses the right scope. A Scope* equal to
|
||||||
|
|
|
@ -19,10 +19,13 @@ struct SimplifyResult
|
||||||
DenseHashSet<TypeId> blockedTypes;
|
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 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
|
enum class Relation
|
||||||
{
|
{
|
||||||
|
|
|
@ -86,6 +86,7 @@ struct TarjanNode
|
||||||
struct Tarjan
|
struct Tarjan
|
||||||
{
|
{
|
||||||
Tarjan();
|
Tarjan();
|
||||||
|
virtual ~Tarjan() = default;
|
||||||
|
|
||||||
// Vertices (types and type packs) are indexed, using pre-order traversal.
|
// Vertices (types and type packs) are indexed, using pre-order traversal.
|
||||||
DenseHashMap<TypeId, int> typeToIndex{nullptr};
|
DenseHashMap<TypeId, int> typeToIndex{nullptr};
|
||||||
|
@ -121,7 +122,7 @@ struct Tarjan
|
||||||
void visitChildren(TypePackId tp, int index);
|
void visitChildren(TypePackId tp, int index);
|
||||||
|
|
||||||
void visitChild(TypeId ty);
|
void visitChild(TypeId ty);
|
||||||
void visitChild(TypePackId ty);
|
void visitChild(TypePackId tp);
|
||||||
|
|
||||||
template<typename Ty>
|
template<typename Ty>
|
||||||
void visitChild(std::optional<Ty> ty)
|
void visitChild(std::optional<Ty> ty)
|
||||||
|
@ -132,7 +133,7 @@ struct Tarjan
|
||||||
|
|
||||||
// Visit the root vertex.
|
// Visit the root vertex.
|
||||||
TarjanResult visitRoot(TypeId ty);
|
TarjanResult visitRoot(TypeId ty);
|
||||||
TarjanResult visitRoot(TypePackId ty);
|
TarjanResult visitRoot(TypePackId tp);
|
||||||
|
|
||||||
// Used to reuse the object for a new operation
|
// Used to reuse the object for a new operation
|
||||||
void clearTarjan(const TxnLog* log);
|
void clearTarjan(const TxnLog* log);
|
||||||
|
@ -150,26 +151,12 @@ struct Tarjan
|
||||||
void visitSCC(int index);
|
void visitSCC(int index);
|
||||||
|
|
||||||
// Each subclass can decide to ignore some nodes.
|
// Each subclass can decide to ignore some nodes.
|
||||||
virtual bool ignoreChildren(TypeId ty)
|
virtual bool ignoreChildren(TypeId ty);
|
||||||
{
|
virtual bool ignoreChildren(TypePackId ty);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual bool ignoreChildren(TypePackId ty)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Some subclasses might ignore children visit, but not other actions like replacing the children
|
// Some subclasses might ignore children visit, but not other actions like replacing the children
|
||||||
virtual bool ignoreChildrenVisit(TypeId ty)
|
virtual bool ignoreChildrenVisit(TypeId ty);
|
||||||
{
|
virtual bool ignoreChildrenVisit(TypePackId ty);
|
||||||
return ignoreChildren(ty);
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual bool ignoreChildrenVisit(TypePackId ty)
|
|
||||||
{
|
|
||||||
return ignoreChildren(ty);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Subclasses should say which vertices are dirty,
|
// Subclasses should say which vertices are dirty,
|
||||||
// and what to do with dirty vertices.
|
// and what to do with dirty vertices.
|
||||||
|
@ -184,6 +171,7 @@ struct Tarjan
|
||||||
struct Substitution : Tarjan
|
struct Substitution : Tarjan
|
||||||
{
|
{
|
||||||
protected:
|
protected:
|
||||||
|
explicit Substitution(TypeArena* arena);
|
||||||
Substitution(const TxnLog* log_, TypeArena* arena);
|
Substitution(const TxnLog* log_, TypeArena* arena);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -232,28 +220,23 @@ public:
|
||||||
virtual TypeId clean(TypeId ty) = 0;
|
virtual TypeId clean(TypeId ty) = 0;
|
||||||
virtual TypePackId clean(TypePackId tp) = 0;
|
virtual TypePackId clean(TypePackId tp) = 0;
|
||||||
|
|
||||||
|
protected:
|
||||||
// Helper functions to create new types (used by subclasses)
|
// Helper functions to create new types (used by subclasses)
|
||||||
template<typename T>
|
template<typename T>
|
||||||
TypeId addType(const T& tv)
|
TypeId addType(T tv)
|
||||||
{
|
{
|
||||||
return arena->addType(tv);
|
return arena->addType(std::move(tv));
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
TypePackId addTypePack(const T& tp)
|
TypePackId addTypePack(T tp)
|
||||||
{
|
{
|
||||||
return arena->addTypePack(TypePackVar{tp});
|
return arena->addTypePack(TypePackVar{std::move(tp)});
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
template<typename Ty>
|
template<typename Ty>
|
||||||
std::optional<Ty> replace(std::optional<Ty> ty)
|
std::optional<Ty> replace(std::optional<Ty> ty);
|
||||||
{
|
|
||||||
if (ty)
|
|
||||||
return replace(*ty);
|
|
||||||
else
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Luau
|
} // namespace Luau
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "Luau/DenseHash.h"
|
||||||
|
#include "Luau/EqSatSimplification.h"
|
||||||
#include "Luau/Set.h"
|
#include "Luau/Set.h"
|
||||||
|
#include "Luau/TypeCheckLimits.h"
|
||||||
|
#include "Luau/TypeFunction.h"
|
||||||
#include "Luau/TypeFwd.h"
|
#include "Luau/TypeFwd.h"
|
||||||
#include "Luau/TypePairHash.h"
|
#include "Luau/TypePairHash.h"
|
||||||
#include "Luau/TypePath.h"
|
#include "Luau/TypePath.h"
|
||||||
#include "Luau/TypeFunction.h"
|
|
||||||
#include "Luau/TypeCheckLimits.h"
|
|
||||||
#include "Luau/DenseHash.h"
|
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
@ -21,7 +22,7 @@ struct InternalErrorReporter;
|
||||||
|
|
||||||
class TypeIds;
|
class TypeIds;
|
||||||
class Normalizer;
|
class Normalizer;
|
||||||
struct NormalizedClassType;
|
struct NormalizedExternType;
|
||||||
struct NormalizedFunctionType;
|
struct NormalizedFunctionType;
|
||||||
struct NormalizedStringType;
|
struct NormalizedStringType;
|
||||||
struct NormalizedType;
|
struct NormalizedType;
|
||||||
|
@ -120,7 +121,7 @@ struct SubtypingEnvironment
|
||||||
DenseHashMap<TypePackId, TypePackId> mappedGenericPacks{nullptr};
|
DenseHashMap<TypePackId, TypePackId> mappedGenericPacks{nullptr};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* See the test cyclic_tables_are_assumed_to_be_compatible_with_classes for
|
* See the test cyclic_tables_are_assumed_to_be_compatible_with_extern_types for
|
||||||
* details.
|
* details.
|
||||||
*
|
*
|
||||||
* An empty value is equivalent to a nonexistent key.
|
* An empty value is equivalent to a nonexistent key.
|
||||||
|
@ -134,6 +135,7 @@ struct Subtyping
|
||||||
{
|
{
|
||||||
NotNull<BuiltinTypes> builtinTypes;
|
NotNull<BuiltinTypes> builtinTypes;
|
||||||
NotNull<TypeArena> arena;
|
NotNull<TypeArena> arena;
|
||||||
|
NotNull<Simplifier> simplifier;
|
||||||
NotNull<Normalizer> normalizer;
|
NotNull<Normalizer> normalizer;
|
||||||
NotNull<TypeFunctionRuntime> typeFunctionRuntime;
|
NotNull<TypeFunctionRuntime> typeFunctionRuntime;
|
||||||
NotNull<InternalErrorReporter> iceReporter;
|
NotNull<InternalErrorReporter> iceReporter;
|
||||||
|
@ -155,6 +157,7 @@ struct Subtyping
|
||||||
Subtyping(
|
Subtyping(
|
||||||
NotNull<BuiltinTypes> builtinTypes,
|
NotNull<BuiltinTypes> builtinTypes,
|
||||||
NotNull<TypeArena> typeArena,
|
NotNull<TypeArena> typeArena,
|
||||||
|
NotNull<Simplifier> simplifier,
|
||||||
NotNull<Normalizer> normalizer,
|
NotNull<Normalizer> normalizer,
|
||||||
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
|
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
|
||||||
NotNull<InternalErrorReporter> iceReporter
|
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 TableType* subTable, const TableType* superTable, NotNull<Scope> scope);
|
||||||
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const MetatableType* subMt, const MetatableType* superMt, NotNull<Scope> scope);
|
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const MetatableType* subMt, const MetatableType* superMt, NotNull<Scope> scope);
|
||||||
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const MetatableType* subMt, const TableType* superTable, NotNull<Scope> scope);
|
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const MetatableType* subMt, const TableType* superTable, NotNull<Scope> scope);
|
||||||
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const ClassType* subClass, const ClassType* superClass, NotNull<Scope> scope);
|
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const ExternType* subExternType, const ExternType* superExternType, NotNull<Scope> scope);
|
||||||
SubtypingResult
|
SubtypingResult isCovariantWith(SubtypingEnvironment& env, TypeId subTy, const ExternType* subExternType, TypeId superTy, const TableType* superTable, NotNull<Scope>);
|
||||||
isCovariantWith(SubtypingEnvironment& env, TypeId subTy, const ClassType* subClass, TypeId superTy, const TableType* superTable, NotNull<Scope>);
|
|
||||||
SubtypingResult isCovariantWith(
|
SubtypingResult isCovariantWith(
|
||||||
SubtypingEnvironment& env,
|
SubtypingEnvironment& env,
|
||||||
const FunctionType* subFunction,
|
const FunctionType* subFunction,
|
||||||
|
@ -256,11 +258,11 @@ private:
|
||||||
);
|
);
|
||||||
SubtypingResult isCovariantWith(
|
SubtypingResult isCovariantWith(
|
||||||
SubtypingEnvironment& env,
|
SubtypingEnvironment& env,
|
||||||
const NormalizedClassType& subClass,
|
const NormalizedExternType& subExternType,
|
||||||
const NormalizedClassType& superClass,
|
const NormalizedExternType& superExternType,
|
||||||
NotNull<Scope> scope
|
NotNull<Scope> scope
|
||||||
);
|
);
|
||||||
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const NormalizedClassType& subClass, const TypeIds& superTables, NotNull<Scope> scope);
|
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const NormalizedExternType& subExternType, const TypeIds& superTables, NotNull<Scope> scope);
|
||||||
SubtypingResult isCovariantWith(
|
SubtypingResult isCovariantWith(
|
||||||
SubtypingEnvironment& env,
|
SubtypingEnvironment& env,
|
||||||
const NormalizedStringType& subString,
|
const NormalizedStringType& subString,
|
||||||
|
|
|
@ -6,12 +6,15 @@
|
||||||
#include "Luau/NotNull.h"
|
#include "Luau/NotNull.h"
|
||||||
#include "Luau/TypeFwd.h"
|
#include "Luau/TypeFwd.h"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
|
||||||
struct TypeArena;
|
struct TypeArena;
|
||||||
struct BuiltinTypes;
|
struct BuiltinTypes;
|
||||||
struct Unifier2;
|
struct Unifier2;
|
||||||
|
struct Subtyping;
|
||||||
class AstExpr;
|
class AstExpr;
|
||||||
|
|
||||||
TypeId matchLiteralType(
|
TypeId matchLiteralType(
|
||||||
|
@ -20,6 +23,7 @@ TypeId matchLiteralType(
|
||||||
NotNull<BuiltinTypes> builtinTypes,
|
NotNull<BuiltinTypes> builtinTypes,
|
||||||
NotNull<TypeArena> arena,
|
NotNull<TypeArena> arena,
|
||||||
NotNull<Unifier2> unifier,
|
NotNull<Unifier2> unifier,
|
||||||
|
NotNull<Subtyping> subtyping,
|
||||||
TypeId expectedType,
|
TypeId expectedType,
|
||||||
TypeId exprType,
|
TypeId exprType,
|
||||||
const AstExpr* expr,
|
const AstExpr* expr,
|
||||||
|
|
|
@ -44,6 +44,7 @@ struct ToStringOptions
|
||||||
bool hideTableKind = false; // If true, all tables will be surrounded with plain '{}'
|
bool hideTableKind = false; // If true, all tables will be surrounded with plain '{}'
|
||||||
bool hideNamedFunctionTypeParameters = false; // If true, type parameters of functions will be hidden at top-level.
|
bool hideNamedFunctionTypeParameters = false; // If true, type parameters of functions will be hidden at top-level.
|
||||||
bool hideFunctionSelfArgument = false; // If true, `self: X` will be omitted from the function signature if the function has self
|
bool hideFunctionSelfArgument = false; // If true, `self: X` will be omitted from the function signature if the function has self
|
||||||
|
bool hideTableAliasExpansions = false; // If true, all table aliases will not be expanded
|
||||||
bool useQuestionMarks = true; // If true, use a postfix ? for options, else write them out as unions that include nil.
|
bool useQuestionMarks = true; // If true, use a postfix ? for options, else write them out as unions that include nil.
|
||||||
size_t maxTableLength = size_t(FInt::LuauTableTypeMaximumStringifierLength); // Only applied to TableTypes
|
size_t maxTableLength = size_t(FInt::LuauTableTypeMaximumStringifierLength); // Only applied to TableTypes
|
||||||
size_t maxTypeLength = size_t(FInt::LuauTypeMaximumStringifierLength);
|
size_t maxTypeLength = size_t(FInt::LuauTypeMaximumStringifierLength);
|
||||||
|
|
|
@ -65,11 +65,10 @@ T* getMutable(PendingTypePack* pending)
|
||||||
// Log of what TypeIds we are rebinding, to be committed later.
|
// Log of what TypeIds we are rebinding, to be committed later.
|
||||||
struct TxnLog
|
struct TxnLog
|
||||||
{
|
{
|
||||||
explicit TxnLog(bool useScopes = false)
|
explicit TxnLog()
|
||||||
: typeVarChanges(nullptr)
|
: typeVarChanges(nullptr)
|
||||||
, typePackChanges(nullptr)
|
, typePackChanges(nullptr)
|
||||||
, ownedSeen()
|
, ownedSeen()
|
||||||
, useScopes(useScopes)
|
|
||||||
, sharedSeen(&ownedSeen)
|
, sharedSeen(&ownedSeen)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -193,16 +192,6 @@ struct TxnLog
|
||||||
// The pointer returned lives until `commit` or `clear` is called.
|
// The pointer returned lives until `commit` or `clear` is called.
|
||||||
PendingTypePack* changeLevel(TypePackId tp, TypeLevel newLevel);
|
PendingTypePack* changeLevel(TypePackId tp, TypeLevel newLevel);
|
||||||
|
|
||||||
// Queues the replacement of a type's scope with the provided scope.
|
|
||||||
//
|
|
||||||
// The pointer returned lives until `commit` or `clear` is called.
|
|
||||||
PendingType* changeScope(TypeId ty, NotNull<Scope> scope);
|
|
||||||
|
|
||||||
// Queues the replacement of a type pack's scope with the provided scope.
|
|
||||||
//
|
|
||||||
// The pointer returned lives until `commit` or `clear` is called.
|
|
||||||
PendingTypePack* changeScope(TypePackId tp, NotNull<Scope> scope);
|
|
||||||
|
|
||||||
// Queues a replacement of a table type with another table type with a new
|
// Queues a replacement of a table type with another table type with a new
|
||||||
// indexer.
|
// indexer.
|
||||||
//
|
//
|
||||||
|
|
|
@ -5,10 +5,11 @@
|
||||||
|
|
||||||
#include "Luau/Ast.h"
|
#include "Luau/Ast.h"
|
||||||
#include "Luau/Common.h"
|
#include "Luau/Common.h"
|
||||||
#include "Luau/Refinement.h"
|
|
||||||
#include "Luau/DenseHash.h"
|
#include "Luau/DenseHash.h"
|
||||||
#include "Luau/NotNull.h"
|
#include "Luau/NotNull.h"
|
||||||
|
#include "Luau/Polarity.h"
|
||||||
#include "Luau/Predicate.h"
|
#include "Luau/Predicate.h"
|
||||||
|
#include "Luau/Refinement.h"
|
||||||
#include "Luau/Unifiable.h"
|
#include "Luau/Unifiable.h"
|
||||||
#include "Luau/Variant.h"
|
#include "Luau/Variant.h"
|
||||||
#include "Luau/VecDeque.h"
|
#include "Luau/VecDeque.h"
|
||||||
|
@ -19,7 +20,6 @@
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <unordered_map>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
LUAU_FASTINT(LuauTableTypeMaximumStringifierLength)
|
LUAU_FASTINT(LuauTableTypeMaximumStringifierLength)
|
||||||
|
@ -69,11 +69,11 @@ using Name = std::string;
|
||||||
// A free type is one whose exact shape has yet to be fully determined.
|
// A free type is one whose exact shape has yet to be fully determined.
|
||||||
struct FreeType
|
struct FreeType
|
||||||
{
|
{
|
||||||
explicit FreeType(TypeLevel level);
|
// New constructors
|
||||||
explicit FreeType(Scope* scope);
|
explicit FreeType(TypeLevel level, TypeId lowerBound, TypeId upperBound);
|
||||||
FreeType(Scope* scope, TypeLevel level);
|
// This one got promoted to explicit
|
||||||
|
explicit FreeType(Scope* scope, TypeId lowerBound, TypeId upperBound, Polarity polarity = Polarity::Unknown);
|
||||||
FreeType(Scope* scope, TypeId lowerBound, TypeId upperBound);
|
explicit FreeType(Scope* scope, TypeLevel level, TypeId lowerBound, TypeId upperBound);
|
||||||
|
|
||||||
int index;
|
int index;
|
||||||
TypeLevel level;
|
TypeLevel level;
|
||||||
|
@ -87,6 +87,8 @@ struct FreeType
|
||||||
// Only used under local type inference
|
// Only used under local type inference
|
||||||
TypeId lowerBound = nullptr;
|
TypeId lowerBound = nullptr;
|
||||||
TypeId upperBound = nullptr;
|
TypeId upperBound = nullptr;
|
||||||
|
|
||||||
|
Polarity polarity = Polarity::Unknown;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct GenericType
|
struct GenericType
|
||||||
|
@ -95,8 +97,8 @@ struct GenericType
|
||||||
GenericType();
|
GenericType();
|
||||||
|
|
||||||
explicit GenericType(TypeLevel level);
|
explicit GenericType(TypeLevel level);
|
||||||
explicit GenericType(const Name& name);
|
explicit GenericType(const Name& name, Polarity polarity = Polarity::Unknown);
|
||||||
explicit GenericType(Scope* scope);
|
explicit GenericType(Scope* scope, Polarity polarity = Polarity::Unknown);
|
||||||
|
|
||||||
GenericType(TypeLevel level, const Name& name);
|
GenericType(TypeLevel level, const Name& name);
|
||||||
GenericType(Scope* scope, const Name& name);
|
GenericType(Scope* scope, const Name& name);
|
||||||
|
@ -106,6 +108,8 @@ struct GenericType
|
||||||
Scope* scope = nullptr;
|
Scope* scope = nullptr;
|
||||||
Name name;
|
Name name;
|
||||||
bool explicitName = false;
|
bool explicitName = false;
|
||||||
|
|
||||||
|
Polarity polarity = Polarity::Unknown;
|
||||||
};
|
};
|
||||||
|
|
||||||
// When an equality constraint is found, it is then "bound" to that type,
|
// When an equality constraint is found, it is then "bound" to that type,
|
||||||
|
@ -131,14 +135,14 @@ struct BlockedType
|
||||||
BlockedType();
|
BlockedType();
|
||||||
int index;
|
int index;
|
||||||
|
|
||||||
Constraint* getOwner() const;
|
const Constraint* getOwner() const;
|
||||||
void setOwner(Constraint* newOwner);
|
void setOwner(const Constraint* newOwner);
|
||||||
void replaceOwner(Constraint* newOwner);
|
void replaceOwner(const Constraint* newOwner);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// The constraint that is intended to unblock this type. Other constraints
|
// The constraint that is intended to unblock this type. Other constraints
|
||||||
// should block on this constraint if present.
|
// should block on this constraint if present.
|
||||||
Constraint* owner = nullptr;
|
const Constraint* owner = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PrimitiveType
|
struct PrimitiveType
|
||||||
|
@ -279,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
|
struct MagicFunctionCallContext
|
||||||
{
|
{
|
||||||
NotNull<struct ConstraintSolver> solver;
|
NotNull<struct ConstraintSolver> solver;
|
||||||
NotNull<const Constraint> constraint;
|
NotNull<const Constraint> constraint;
|
||||||
const class AstExprCall* callSite;
|
NotNull<const AstExprCall> callSite;
|
||||||
TypePackId arguments;
|
TypePackId arguments;
|
||||||
TypePackId result;
|
TypePackId result;
|
||||||
};
|
};
|
||||||
|
|
||||||
using DcrMagicFunction = std::function<bool(MagicFunctionCallContext)>;
|
|
||||||
struct MagicRefinementContext
|
struct MagicRefinementContext
|
||||||
{
|
{
|
||||||
NotNull<Scope> scope;
|
NotNull<Scope> scope;
|
||||||
|
@ -308,8 +308,30 @@ struct MagicFunctionTypeCheckContext
|
||||||
NotNull<Scope> checkScope;
|
NotNull<Scope> checkScope;
|
||||||
};
|
};
|
||||||
|
|
||||||
using DcrMagicRefinement = void (*)(const MagicRefinementContext&);
|
struct MagicFunction
|
||||||
using DcrMagicFunctionTypeCheck = std::function<void(const MagicFunctionTypeCheckContext&)>;
|
{
|
||||||
|
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
|
struct FunctionType
|
||||||
{
|
{
|
||||||
// Global monomorphic function
|
// Global monomorphic function
|
||||||
|
@ -326,10 +348,8 @@ struct FunctionType
|
||||||
);
|
);
|
||||||
|
|
||||||
// Local monomorphic function
|
// Local monomorphic function
|
||||||
FunctionType(TypeLevel level, TypePackId argTypes, TypePackId retTypes, std::optional<FunctionDefinition> defn = {}, bool hasSelf = false);
|
|
||||||
FunctionType(
|
FunctionType(
|
||||||
TypeLevel level,
|
TypeLevel level,
|
||||||
Scope* scope,
|
|
||||||
TypePackId argTypes,
|
TypePackId argTypes,
|
||||||
TypePackId retTypes,
|
TypePackId retTypes,
|
||||||
std::optional<FunctionDefinition> defn = {},
|
std::optional<FunctionDefinition> defn = {},
|
||||||
|
@ -346,16 +366,6 @@ struct FunctionType
|
||||||
std::optional<FunctionDefinition> defn = {},
|
std::optional<FunctionDefinition> defn = {},
|
||||||
bool hasSelf = false
|
bool hasSelf = false
|
||||||
);
|
);
|
||||||
FunctionType(
|
|
||||||
TypeLevel level,
|
|
||||||
Scope* scope,
|
|
||||||
std::vector<TypeId> generics,
|
|
||||||
std::vector<TypePackId> genericPacks,
|
|
||||||
TypePackId argTypes,
|
|
||||||
TypePackId retTypes,
|
|
||||||
std::optional<FunctionDefinition> defn = {},
|
|
||||||
bool hasSelf = false
|
|
||||||
);
|
|
||||||
|
|
||||||
std::optional<FunctionDefinition> definition;
|
std::optional<FunctionDefinition> definition;
|
||||||
/// These should all be generic
|
/// These should all be generic
|
||||||
|
@ -364,25 +374,16 @@ struct FunctionType
|
||||||
std::vector<std::optional<FunctionArgument>> argNames;
|
std::vector<std::optional<FunctionArgument>> argNames;
|
||||||
Tags tags;
|
Tags tags;
|
||||||
TypeLevel level;
|
TypeLevel level;
|
||||||
Scope* scope = nullptr;
|
|
||||||
TypePackId argTypes;
|
TypePackId argTypes;
|
||||||
TypePackId retTypes;
|
TypePackId retTypes;
|
||||||
MagicFunction magicFunction = nullptr;
|
std::shared_ptr<MagicFunction> magic = 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;
|
|
||||||
|
|
||||||
bool hasSelf;
|
bool hasSelf;
|
||||||
// `hasNoFreeOrGenericTypes` should be true if and only if the type does not have any free or generic types present inside it.
|
// `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.
|
// this flag is used as an optimization to exit early from procedures that manipulate free or generic types.
|
||||||
bool hasNoFreeOrGenericTypes = false;
|
bool hasNoFreeOrGenericTypes = false;
|
||||||
bool isCheckedFunction = false;
|
bool isCheckedFunction = false;
|
||||||
|
bool isDeprecatedFunction = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class TableState
|
enum class TableState
|
||||||
|
@ -459,7 +460,9 @@ struct Property
|
||||||
TypeId type() const;
|
TypeId type() const;
|
||||||
void setType(TypeId ty);
|
void setType(TypeId ty);
|
||||||
|
|
||||||
// Sets the write type of this property to the read type.
|
// If this property has a present `writeTy`, set it equal to the `readTy`.
|
||||||
|
// This is to ensure that if we normalize a property that has divergent
|
||||||
|
// read and write types, we make them converge (for now).
|
||||||
void makeShared();
|
void makeShared();
|
||||||
|
|
||||||
bool isShared() const;
|
bool isShared() const;
|
||||||
|
@ -504,9 +507,6 @@ struct TableType
|
||||||
std::optional<TypeId> boundTo;
|
std::optional<TypeId> boundTo;
|
||||||
Tags tags;
|
Tags tags;
|
||||||
|
|
||||||
// Methods of this table that have an untyped self will use the same shared self type.
|
|
||||||
std::optional<TypeId> selfTy;
|
|
||||||
|
|
||||||
// We track the number of as-yet-unadded properties to unsealed tables.
|
// We track the number of as-yet-unadded properties to unsealed tables.
|
||||||
// Some constraints will use this information to decide whether or not they
|
// Some constraints will use this information to decide whether or not they
|
||||||
// are able to dispatch.
|
// are able to dispatch.
|
||||||
|
@ -532,15 +532,15 @@ struct ClassUserData
|
||||||
virtual ~ClassUserData() {}
|
virtual ~ClassUserData() {}
|
||||||
};
|
};
|
||||||
|
|
||||||
/** The type of a class.
|
/** The type of an external userdata exposed to Luau.
|
||||||
*
|
*
|
||||||
* Classes behave like tables in many ways, but there are some important differences:
|
* Extern types behave like tables in many ways, but there are some important differences:
|
||||||
*
|
*
|
||||||
* The properties of a class are always exactly known.
|
* The properties of a class are always exactly known.
|
||||||
* Classes optionally have a parent class.
|
* Extern types optionally have a parent type.
|
||||||
* Two different classes that share the same properties are nevertheless distinct and mutually incompatible.
|
* Two different extern types that share the same properties are nevertheless distinct and mutually incompatible.
|
||||||
*/
|
*/
|
||||||
struct ClassType
|
struct ExternType
|
||||||
{
|
{
|
||||||
using Props = TableType::Props;
|
using Props = TableType::Props;
|
||||||
|
|
||||||
|
@ -554,7 +554,7 @@ struct ClassType
|
||||||
std::optional<Location> definitionLocation;
|
std::optional<Location> definitionLocation;
|
||||||
std::optional<TableIndexer> indexer;
|
std::optional<TableIndexer> indexer;
|
||||||
|
|
||||||
ClassType(
|
ExternType(
|
||||||
Name name,
|
Name name,
|
||||||
Props props,
|
Props props,
|
||||||
std::optional<TypeId> parent,
|
std::optional<TypeId> parent,
|
||||||
|
@ -575,7 +575,7 @@ struct ClassType
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
ClassType(
|
ExternType(
|
||||||
Name name,
|
Name name,
|
||||||
Props props,
|
Props props,
|
||||||
std::optional<TypeId> parent,
|
std::optional<TypeId> parent,
|
||||||
|
@ -608,7 +608,7 @@ struct UserDefinedFunctionData
|
||||||
// References to AST elements are owned by the Module allocator which also stores this type
|
// References to AST elements are owned by the Module allocator which also stores this type
|
||||||
AstStatTypeFunction* definition = nullptr;
|
AstStatTypeFunction* definition = nullptr;
|
||||||
|
|
||||||
DenseHashMap<Name, AstStatTypeFunction*> environment{""};
|
DenseHashMap<Name, std::pair<AstStatTypeFunction*, size_t>> environment{""};
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -625,7 +625,7 @@ struct TypeFunctionInstanceType
|
||||||
std::vector<TypeId> typeArguments;
|
std::vector<TypeId> typeArguments;
|
||||||
std::vector<TypePackId> packArguments;
|
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;
|
UserDefinedFunctionData userFuncData;
|
||||||
|
|
||||||
TypeFunctionInstanceType(
|
TypeFunctionInstanceType(
|
||||||
|
@ -762,7 +762,7 @@ struct NegationType
|
||||||
TypeId ty;
|
TypeId ty;
|
||||||
};
|
};
|
||||||
|
|
||||||
using ErrorType = Unifiable::Error;
|
using ErrorType = Unifiable::Error<TypeId>;
|
||||||
|
|
||||||
using TypeVariant = Unifiable::Variant<
|
using TypeVariant = Unifiable::Variant<
|
||||||
TypeId,
|
TypeId,
|
||||||
|
@ -775,7 +775,7 @@ using TypeVariant = Unifiable::Variant<
|
||||||
FunctionType,
|
FunctionType,
|
||||||
TableType,
|
TableType,
|
||||||
MetatableType,
|
MetatableType,
|
||||||
ClassType,
|
ExternType,
|
||||||
AnyType,
|
AnyType,
|
||||||
UnionType,
|
UnionType,
|
||||||
IntersectionType,
|
IntersectionType,
|
||||||
|
@ -868,6 +868,9 @@ struct TypeFun
|
||||||
*/
|
*/
|
||||||
TypeId type;
|
TypeId type;
|
||||||
|
|
||||||
|
// The location of where this TypeFun was defined, if available
|
||||||
|
std::optional<Location> definitionLocation;
|
||||||
|
|
||||||
TypeFun() = default;
|
TypeFun() = default;
|
||||||
|
|
||||||
explicit TypeFun(TypeId ty)
|
explicit TypeFun(TypeId ty)
|
||||||
|
@ -875,16 +878,23 @@ struct TypeFun
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
TypeFun(std::vector<GenericTypeDefinition> typeParams, TypeId type)
|
TypeFun(std::vector<GenericTypeDefinition> typeParams, TypeId type, std::optional<Location> definitionLocation = std::nullopt)
|
||||||
: typeParams(std::move(typeParams))
|
: typeParams(std::move(typeParams))
|
||||||
, type(type)
|
, type(type)
|
||||||
|
, definitionLocation(definitionLocation)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
TypeFun(std::vector<GenericTypeDefinition> typeParams, std::vector<GenericTypePackDefinition> typePackParams, TypeId type)
|
TypeFun(
|
||||||
|
std::vector<GenericTypeDefinition> typeParams,
|
||||||
|
std::vector<GenericTypePackDefinition> typePackParams,
|
||||||
|
TypeId type,
|
||||||
|
std::optional<Location> definitionLocation = std::nullopt
|
||||||
|
)
|
||||||
: typeParams(std::move(typeParams))
|
: typeParams(std::move(typeParams))
|
||||||
, typePackParams(std::move(typePackParams))
|
, typePackParams(std::move(typePackParams))
|
||||||
, type(type)
|
, type(type)
|
||||||
|
, definitionLocation(definitionLocation)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -976,7 +986,7 @@ public:
|
||||||
const TypeId threadType;
|
const TypeId threadType;
|
||||||
const TypeId bufferType;
|
const TypeId bufferType;
|
||||||
const TypeId functionType;
|
const TypeId functionType;
|
||||||
const TypeId classType;
|
const TypeId externType;
|
||||||
const TypeId tableType;
|
const TypeId tableType;
|
||||||
const TypeId emptyTableType;
|
const TypeId emptyTableType;
|
||||||
const TypeId trueType;
|
const TypeId trueType;
|
||||||
|
@ -988,6 +998,7 @@ public:
|
||||||
const TypeId noRefineType;
|
const TypeId noRefineType;
|
||||||
const TypeId falsyType;
|
const TypeId falsyType;
|
||||||
const TypeId truthyType;
|
const TypeId truthyType;
|
||||||
|
const TypeId notNilType;
|
||||||
|
|
||||||
const TypeId optionalNumberType;
|
const TypeId optionalNumberType;
|
||||||
const TypeId optionalStringType;
|
const TypeId optionalStringType;
|
||||||
|
@ -1008,10 +1019,10 @@ TypeLevel* getMutableLevel(TypeId ty);
|
||||||
|
|
||||||
std::optional<TypeLevel> getLevel(TypePackId tp);
|
std::optional<TypeLevel> getLevel(TypePackId tp);
|
||||||
|
|
||||||
const Property* lookupClassProp(const ClassType* cls, const Name& name);
|
const Property* lookupExternTypeProp(const ExternType* cls, const Name& name);
|
||||||
|
|
||||||
// Whether `cls` is a subclass of `parent`
|
// Whether `cls` is a subclass of `parent`
|
||||||
bool isSubclass(const ClassType* cls, const ClassType* parent);
|
bool isSubclass(const ExternType* cls, const ExternType* parent);
|
||||||
|
|
||||||
Type* asMutable(TypeId ty);
|
Type* asMutable(TypeId ty);
|
||||||
|
|
||||||
|
@ -1188,7 +1199,7 @@ private:
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
TypeId freshType(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, Scope* scope);
|
TypeId freshType(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, Scope* scope, Polarity polarity = Polarity::Unknown);
|
||||||
|
|
||||||
using TypeIdPredicate = std::function<std::optional<TypeId>(TypeId)>;
|
using TypeIdPredicate = std::function<std::optional<TypeId>(TypeId)>;
|
||||||
std::vector<TypeId> filterMap(TypeId type, TypeIdPredicate predicate);
|
std::vector<TypeId> filterMap(TypeId type, TypeIdPredicate predicate);
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "Luau/Polarity.h"
|
||||||
#include "Luau/TypedAllocator.h"
|
#include "Luau/TypedAllocator.h"
|
||||||
#include "Luau/Type.h"
|
#include "Luau/Type.h"
|
||||||
#include "Luau/TypePack.h"
|
#include "Luau/TypePack.h"
|
||||||
|
@ -32,11 +33,11 @@ struct TypeArena
|
||||||
|
|
||||||
TypeId addTV(Type&& tv);
|
TypeId addTV(Type&& tv);
|
||||||
|
|
||||||
TypeId freshType(TypeLevel level);
|
TypeId freshType(NotNull<BuiltinTypes> builtins, TypeLevel level);
|
||||||
TypeId freshType(Scope* scope);
|
TypeId freshType(NotNull<BuiltinTypes> builtins, Scope* scope);
|
||||||
TypeId freshType(Scope* scope, TypeLevel level);
|
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::initializer_list<TypeId> types);
|
||||||
TypePackId addTypePack(std::vector<TypeId> types, std::optional<TypePackId> tail = {});
|
TypePackId addTypePack(std::vector<TypeId> types, std::optional<TypePackId> tail = {});
|
||||||
|
|
|
@ -11,7 +11,7 @@ namespace Luau
|
||||||
struct TypeRehydrationOptions
|
struct TypeRehydrationOptions
|
||||||
{
|
{
|
||||||
std::unordered_set<std::string> bannedNames;
|
std::unordered_set<std::string> bannedNames;
|
||||||
bool expandClassProps = false;
|
bool expandExternTypeProps = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
void attachTypeData(SourceModule& source, Module& result);
|
void attachTypeData(SourceModule& source, Module& result);
|
||||||
|
|
|
@ -2,15 +2,18 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Luau/Error.h"
|
|
||||||
#include "Luau/NotNull.h"
|
|
||||||
#include "Luau/Common.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/Type.h"
|
||||||
#include "Luau/TypeFwd.h"
|
#include "Luau/TypeFwd.h"
|
||||||
#include "Luau/TypeOrPack.h"
|
#include "Luau/TypeOrPack.h"
|
||||||
#include "Luau/Normalize.h"
|
#include "Luau/TypeUtils.h"
|
||||||
#include "Luau/Subtyping.h"
|
|
||||||
|
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
@ -37,18 +40,29 @@ struct Reasonings
|
||||||
|
|
||||||
std::string toString()
|
std::string toString()
|
||||||
{
|
{
|
||||||
|
if (FFlag::LuauImproveTypePathsInErrors && reasons.empty())
|
||||||
|
return "";
|
||||||
|
|
||||||
// DenseHashSet ordering is entirely undefined, so we want to
|
// DenseHashSet ordering is entirely undefined, so we want to
|
||||||
// sort the reasons here to achieve a stable error
|
// sort the reasons here to achieve a stable error
|
||||||
// stringification.
|
// stringification.
|
||||||
std::sort(reasons.begin(), reasons.end());
|
std::sort(reasons.begin(), reasons.end());
|
||||||
std::string allReasons;
|
std::string allReasons = FFlag::LuauImproveTypePathsInErrors ? "\nthis is because " : "";
|
||||||
bool first = true;
|
bool first = true;
|
||||||
for (const std::string& reason : reasons)
|
for (const std::string& reason : reasons)
|
||||||
{
|
{
|
||||||
if (first)
|
if (FFlag::LuauImproveTypePathsInErrors)
|
||||||
first = false;
|
{
|
||||||
|
if (reasons.size() > 1)
|
||||||
|
allReasons += "\n\t * ";
|
||||||
|
}
|
||||||
else
|
else
|
||||||
allReasons += "\n\t";
|
{
|
||||||
|
if (first)
|
||||||
|
first = false;
|
||||||
|
else
|
||||||
|
allReasons += "\n\t";
|
||||||
|
}
|
||||||
|
|
||||||
allReasons += reason;
|
allReasons += reason;
|
||||||
}
|
}
|
||||||
|
@ -60,8 +74,9 @@ struct Reasonings
|
||||||
|
|
||||||
void check(
|
void check(
|
||||||
NotNull<BuiltinTypes> builtinTypes,
|
NotNull<BuiltinTypes> builtinTypes,
|
||||||
|
NotNull<Simplifier> simplifier,
|
||||||
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
|
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
|
||||||
NotNull<UnifierSharedState> sharedState,
|
NotNull<UnifierSharedState> unifierState,
|
||||||
NotNull<TypeCheckLimits> limits,
|
NotNull<TypeCheckLimits> limits,
|
||||||
DcrLogger* logger,
|
DcrLogger* logger,
|
||||||
const SourceModule& sourceModule,
|
const SourceModule& sourceModule,
|
||||||
|
@ -71,6 +86,7 @@ void check(
|
||||||
struct TypeChecker2
|
struct TypeChecker2
|
||||||
{
|
{
|
||||||
NotNull<BuiltinTypes> builtinTypes;
|
NotNull<BuiltinTypes> builtinTypes;
|
||||||
|
NotNull<Simplifier> simplifier;
|
||||||
NotNull<TypeFunctionRuntime> typeFunctionRuntime;
|
NotNull<TypeFunctionRuntime> typeFunctionRuntime;
|
||||||
DcrLogger* logger;
|
DcrLogger* logger;
|
||||||
const NotNull<TypeCheckLimits> limits;
|
const NotNull<TypeCheckLimits> limits;
|
||||||
|
@ -90,6 +106,7 @@ struct TypeChecker2
|
||||||
|
|
||||||
TypeChecker2(
|
TypeChecker2(
|
||||||
NotNull<BuiltinTypes> builtinTypes,
|
NotNull<BuiltinTypes> builtinTypes,
|
||||||
|
NotNull<Simplifier> simplifier,
|
||||||
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
|
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
|
||||||
NotNull<UnifierSharedState> unifierState,
|
NotNull<UnifierSharedState> unifierState,
|
||||||
NotNull<TypeCheckLimits> limits,
|
NotNull<TypeCheckLimits> limits,
|
||||||
|
@ -112,14 +129,14 @@ private:
|
||||||
std::optional<StackPusher> pushStack(AstNode* node);
|
std::optional<StackPusher> pushStack(AstNode* node);
|
||||||
void checkForInternalTypeFunction(TypeId ty, Location location);
|
void checkForInternalTypeFunction(TypeId ty, Location location);
|
||||||
TypeId checkForTypeFunctionInhabitance(TypeId instance, Location location);
|
TypeId checkForTypeFunctionInhabitance(TypeId instance, Location location);
|
||||||
TypePackId lookupPack(AstExpr* expr);
|
TypePackId lookupPack(AstExpr* expr) const;
|
||||||
TypeId lookupType(AstExpr* expr);
|
TypeId lookupType(AstExpr* expr);
|
||||||
TypeId lookupAnnotation(AstType* annotation);
|
TypeId lookupAnnotation(AstType* annotation);
|
||||||
std::optional<TypePackId> lookupPackAnnotation(AstTypePack* annotation);
|
std::optional<TypePackId> lookupPackAnnotation(AstTypePack* annotation) const;
|
||||||
TypeId lookupExpectedType(AstExpr* expr);
|
TypeId lookupExpectedType(AstExpr* expr) const;
|
||||||
TypePackId lookupExpectedPack(AstExpr* expr, TypeArena& arena);
|
TypePackId lookupExpectedPack(AstExpr* expr, TypeArena& arena) const;
|
||||||
TypePackId reconstructPack(AstArray<AstExpr*> exprs, TypeArena& arena);
|
TypePackId reconstructPack(AstArray<AstExpr*> exprs, TypeArena& arena);
|
||||||
Scope* findInnermostScope(Location location);
|
Scope* findInnermostScope(Location location) const;
|
||||||
void visit(AstStat* stat);
|
void visit(AstStat* stat);
|
||||||
void visit(AstStatIf* ifStatement);
|
void visit(AstStatIf* ifStatement);
|
||||||
void visit(AstStatWhile* whileStatement);
|
void visit(AstStatWhile* whileStatement);
|
||||||
|
@ -143,7 +160,7 @@ private:
|
||||||
void visit(AstTypeList types);
|
void visit(AstTypeList types);
|
||||||
void visit(AstStatDeclareFunction* stat);
|
void visit(AstStatDeclareFunction* stat);
|
||||||
void visit(AstStatDeclareGlobal* stat);
|
void visit(AstStatDeclareGlobal* stat);
|
||||||
void visit(AstStatDeclareClass* stat);
|
void visit(AstStatDeclareExternType* stat);
|
||||||
void visit(AstStatError* stat);
|
void visit(AstStatError* stat);
|
||||||
void visit(AstExpr* expr, ValueContext context);
|
void visit(AstExpr* expr, ValueContext context);
|
||||||
void visit(AstExprGroup* expr, ValueContext context);
|
void visit(AstExprGroup* expr, ValueContext context);
|
||||||
|
@ -156,7 +173,7 @@ private:
|
||||||
void visit(AstExprVarargs* expr);
|
void visit(AstExprVarargs* expr);
|
||||||
void visitCall(AstExprCall* call);
|
void visitCall(AstExprCall* call);
|
||||||
void visit(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);
|
TypeId stripFromNilAndReport(TypeId ty, const Location& location);
|
||||||
void visitExprName(AstExpr* expr, Location location, const std::string& propName, ValueContext context, TypeId astIndexExprTy);
|
void visitExprName(AstExpr* expr, Location location, const std::string& propName, ValueContext context, TypeId astIndexExprTy);
|
||||||
void visit(AstExprIndexName* indexName, ValueContext context);
|
void visit(AstExprIndexName* indexName, ValueContext context);
|
||||||
|
@ -171,7 +188,7 @@ private:
|
||||||
void visit(AstExprInterpString* interpString);
|
void visit(AstExprInterpString* interpString);
|
||||||
void visit(AstExprError* expr);
|
void visit(AstExprError* expr);
|
||||||
TypeId flattenPack(TypePackId pack);
|
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(AstType* ty);
|
||||||
void visit(AstTypeReference* ty);
|
void visit(AstTypeReference* ty);
|
||||||
void visit(AstTypeTable* table);
|
void visit(AstTypeTable* table);
|
||||||
|
@ -213,6 +230,9 @@ private:
|
||||||
std::vector<TypeError>& errors
|
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;
|
void diagnoseMissingTableKey(UnknownProperty* utk, TypeErrorData& data) const;
|
||||||
bool isErrorSuppressing(Location loc, TypeId ty);
|
bool isErrorSuppressing(Location loc, TypeId ty);
|
||||||
bool isErrorSuppressing(Location loc1, TypeId ty1, Location loc2, TypeId ty2);
|
bool isErrorSuppressing(Location loc1, TypeId ty1, Location loc2, TypeId ty2);
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Luau/Constraint.h"
|
#include "Luau/Constraint.h"
|
||||||
|
#include "Luau/EqSatSimplification.h"
|
||||||
#include "Luau/Error.h"
|
#include "Luau/Error.h"
|
||||||
#include "Luau/NotNull.h"
|
#include "Luau/NotNull.h"
|
||||||
#include "Luau/TypeCheckLimits.h"
|
#include "Luau/TypeCheckLimits.h"
|
||||||
|
@ -41,9 +42,18 @@ struct TypeFunctionRuntime
|
||||||
|
|
||||||
StateRef state;
|
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
|
// Evaluation of type functions should only be performed in the absence of parse errors in the source module
|
||||||
bool allowEvaluation = true;
|
bool allowEvaluation = true;
|
||||||
|
|
||||||
|
// Root scope in which the type function operates in, set up by ConstraintGenerator
|
||||||
|
ScopePtr rootScope;
|
||||||
|
|
||||||
|
// Output created by 'print' function
|
||||||
|
std::vector<std::string> messages;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void prepareState();
|
void prepareState();
|
||||||
};
|
};
|
||||||
|
@ -53,6 +63,7 @@ struct TypeFunctionContext
|
||||||
NotNull<TypeArena> arena;
|
NotNull<TypeArena> arena;
|
||||||
NotNull<BuiltinTypes> builtins;
|
NotNull<BuiltinTypes> builtins;
|
||||||
NotNull<Scope> scope;
|
NotNull<Scope> scope;
|
||||||
|
NotNull<Simplifier> simplifier;
|
||||||
NotNull<Normalizer> normalizer;
|
NotNull<Normalizer> normalizer;
|
||||||
NotNull<TypeFunctionRuntime> typeFunctionRuntime;
|
NotNull<TypeFunctionRuntime> typeFunctionRuntime;
|
||||||
NotNull<InternalErrorReporter> ice;
|
NotNull<InternalErrorReporter> ice;
|
||||||
|
@ -63,7 +74,7 @@ struct TypeFunctionContext
|
||||||
// The constraint being reduced in this run of the reduction
|
// The constraint being reduced in this run of the reduction
|
||||||
const Constraint* constraint;
|
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);
|
TypeFunctionContext(NotNull<ConstraintSolver> cs, NotNull<Scope> scope, NotNull<const Constraint> constraint);
|
||||||
|
|
||||||
|
@ -71,6 +82,7 @@ struct TypeFunctionContext
|
||||||
NotNull<TypeArena> arena,
|
NotNull<TypeArena> arena,
|
||||||
NotNull<BuiltinTypes> builtins,
|
NotNull<BuiltinTypes> builtins,
|
||||||
NotNull<Scope> scope,
|
NotNull<Scope> scope,
|
||||||
|
NotNull<Simplifier> simplifier,
|
||||||
NotNull<Normalizer> normalizer,
|
NotNull<Normalizer> normalizer,
|
||||||
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
|
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
|
||||||
NotNull<InternalErrorReporter> ice,
|
NotNull<InternalErrorReporter> ice,
|
||||||
|
@ -79,6 +91,7 @@ struct TypeFunctionContext
|
||||||
: arena(arena)
|
: arena(arena)
|
||||||
, builtins(builtins)
|
, builtins(builtins)
|
||||||
, scope(scope)
|
, scope(scope)
|
||||||
|
, simplifier(simplifier)
|
||||||
, normalizer(normalizer)
|
, normalizer(normalizer)
|
||||||
, typeFunctionRuntime(typeFunctionRuntime)
|
, typeFunctionRuntime(typeFunctionRuntime)
|
||||||
, ice(ice)
|
, ice(ice)
|
||||||
|
@ -91,19 +104,31 @@ struct TypeFunctionContext
|
||||||
NotNull<Constraint> pushConstraint(ConstraintV&& c) const;
|
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,
|
/// Represents a reduction result, which may have successfully reduced the type,
|
||||||
/// may have concretely failed to reduce the type, or may simply be stuck
|
/// may have concretely failed to reduce the type, or may simply be stuck
|
||||||
/// without more information.
|
/// without more information.
|
||||||
template<typename Ty>
|
template<typename Ty>
|
||||||
struct TypeFunctionReductionResult
|
struct TypeFunctionReductionResult
|
||||||
{
|
{
|
||||||
|
|
||||||
/// The result of the reduction, if any. If this is nullopt, the type function
|
/// The result of the reduction, if any. If this is nullopt, the type function
|
||||||
/// could not be reduced.
|
/// could not be reduced.
|
||||||
std::optional<Ty> result;
|
std::optional<Ty> result;
|
||||||
/// Whether the result is uninhabited: whether we know, unambiguously and
|
/// Indicates the status of this reduction: is `Reduction::Irreducible` if
|
||||||
/// permanently, whether this type function reduction results in an
|
/// the this result indicates the type function is irreducible, and
|
||||||
/// uninhabitable type. This will trigger an error to be reported.
|
/// `Reduction::Erroneous` if this result indicates the type function is
|
||||||
bool uninhabited;
|
/// erroneous. `Reduction::MaybeOk` otherwise.
|
||||||
|
Reduction reductionStatus;
|
||||||
/// Any types that need to be progressed or mutated before the reduction may
|
/// Any types that need to be progressed or mutated before the reduction may
|
||||||
/// proceed.
|
/// proceed.
|
||||||
std::vector<TypeId> blockedTypes;
|
std::vector<TypeId> blockedTypes;
|
||||||
|
@ -112,6 +137,8 @@ struct TypeFunctionReductionResult
|
||||||
std::vector<TypePackId> blockedPacks;
|
std::vector<TypePackId> blockedPacks;
|
||||||
/// A runtime error message from user-defined type functions
|
/// A runtime error message from user-defined type functions
|
||||||
std::optional<std::string> error;
|
std::optional<std::string> error;
|
||||||
|
/// Messages printed out from user-defined type functions
|
||||||
|
std::vector<std::string> messages;
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
|
@ -128,6 +155,9 @@ struct TypeFunction
|
||||||
|
|
||||||
/// The reducer function for the type function.
|
/// The reducer function for the type function.
|
||||||
ReducerFunction<TypeId> reducer;
|
ReducerFunction<TypeId> reducer;
|
||||||
|
|
||||||
|
/// If true, this type function can reduce even if it is parameterized on a generic.
|
||||||
|
bool canReduceGenerics = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Represents a type function that may be applied to map a series of types and
|
/// Represents a type function that may be applied to map a series of types and
|
||||||
|
@ -140,15 +170,20 @@ struct TypePackFunction
|
||||||
|
|
||||||
/// The reducer function for the type pack function.
|
/// The reducer function for the type pack function.
|
||||||
ReducerFunction<TypePackId> reducer;
|
ReducerFunction<TypePackId> reducer;
|
||||||
|
|
||||||
|
/// If true, this type function can reduce even if it is parameterized on a generic.
|
||||||
|
bool canReduceGenerics = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct FunctionGraphReductionResult
|
struct FunctionGraphReductionResult
|
||||||
{
|
{
|
||||||
ErrorVec errors;
|
ErrorVec errors;
|
||||||
|
ErrorVec messages;
|
||||||
DenseHashSet<TypeId> blockedTypes{nullptr};
|
DenseHashSet<TypeId> blockedTypes{nullptr};
|
||||||
DenseHashSet<TypePackId> blockedPacks{nullptr};
|
DenseHashSet<TypePackId> blockedPacks{nullptr};
|
||||||
DenseHashSet<TypeId> reducedTypes{nullptr};
|
DenseHashSet<TypeId> reducedTypes{nullptr};
|
||||||
DenseHashSet<TypePackId> reducedPacks{nullptr};
|
DenseHashSet<TypePackId> reducedPacks{nullptr};
|
||||||
|
DenseHashSet<TypeId> irreducibleTypes{nullptr};
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -216,6 +251,11 @@ struct BuiltinTypeFunctions
|
||||||
TypeFunction indexFunc;
|
TypeFunction indexFunc;
|
||||||
TypeFunction rawgetFunc;
|
TypeFunction rawgetFunc;
|
||||||
|
|
||||||
|
TypeFunction setmetatableFunc;
|
||||||
|
TypeFunction getmetatableFunc;
|
||||||
|
|
||||||
|
TypeFunction weakoptionalFunc;
|
||||||
|
|
||||||
void addToScope(NotNull<TypeArena> arena, NotNull<Scope> scope) const;
|
void addToScope(NotNull<TypeArena> arena, NotNull<Scope> scope) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
#include "Luau/Common.h"
|
#include "Luau/Common.h"
|
||||||
#include "Luau/Variant.h"
|
#include "Luau/Variant.h"
|
||||||
|
#include "Luau/TypeFwd.h"
|
||||||
|
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
@ -31,6 +32,8 @@ struct TypeFunctionPrimitiveType
|
||||||
Boolean,
|
Boolean,
|
||||||
Number,
|
Number,
|
||||||
String,
|
String,
|
||||||
|
Thread,
|
||||||
|
Buffer,
|
||||||
};
|
};
|
||||||
|
|
||||||
Type type;
|
Type type;
|
||||||
|
@ -117,7 +120,14 @@ struct TypeFunctionVariadicTypePack
|
||||||
TypeFunctionTypeId type;
|
TypeFunctionTypeId type;
|
||||||
};
|
};
|
||||||
|
|
||||||
using TypeFunctionTypePackVariant = Variant<TypeFunctionTypePack, TypeFunctionVariadicTypePack>;
|
struct TypeFunctionGenericTypePack
|
||||||
|
{
|
||||||
|
bool isNamed = false;
|
||||||
|
|
||||||
|
std::string name;
|
||||||
|
};
|
||||||
|
|
||||||
|
using TypeFunctionTypePackVariant = Variant<TypeFunctionTypePack, TypeFunctionVariadicTypePack, TypeFunctionGenericTypePack>;
|
||||||
|
|
||||||
struct TypeFunctionTypePackVar
|
struct TypeFunctionTypePackVar
|
||||||
{
|
{
|
||||||
|
@ -133,6 +143,9 @@ struct TypeFunctionTypePackVar
|
||||||
|
|
||||||
struct TypeFunctionFunctionType
|
struct TypeFunctionFunctionType
|
||||||
{
|
{
|
||||||
|
std::vector<TypeFunctionTypeId> generics;
|
||||||
|
std::vector<TypeFunctionTypePackId> genericPacks;
|
||||||
|
|
||||||
TypeFunctionTypePackId argTypes;
|
TypeFunctionTypePackId argTypes;
|
||||||
TypeFunctionTypePackId retTypes;
|
TypeFunctionTypePackId retTypes;
|
||||||
};
|
};
|
||||||
|
@ -192,7 +205,7 @@ struct TypeFunctionTableType
|
||||||
std::optional<TypeFunctionTypeId> metatable;
|
std::optional<TypeFunctionTypeId> metatable;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct TypeFunctionClassType
|
struct TypeFunctionExternType
|
||||||
{
|
{
|
||||||
using Name = std::string;
|
using Name = std::string;
|
||||||
using Props = std::map<Name, TypeFunctionProperty>;
|
using Props = std::map<Name, TypeFunctionProperty>;
|
||||||
|
@ -203,7 +216,19 @@ struct TypeFunctionClassType
|
||||||
|
|
||||||
std::optional<TypeFunctionTypeId> metatable; // metaclass?
|
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;
|
std::string name;
|
||||||
};
|
};
|
||||||
|
@ -219,7 +244,8 @@ using TypeFunctionTypeVariant = Luau::Variant<
|
||||||
TypeFunctionNegationType,
|
TypeFunctionNegationType,
|
||||||
TypeFunctionFunctionType,
|
TypeFunctionFunctionType,
|
||||||
TypeFunctionTableType,
|
TypeFunctionTableType,
|
||||||
TypeFunctionClassType>;
|
TypeFunctionExternType,
|
||||||
|
TypeFunctionGenericType>;
|
||||||
|
|
||||||
struct TypeFunctionType
|
struct TypeFunctionType
|
||||||
{
|
{
|
||||||
|
|
|
@ -28,20 +28,12 @@ struct TypeFunctionRuntimeBuilderState
|
||||||
{
|
{
|
||||||
NotNull<TypeFunctionContext> ctx;
|
NotNull<TypeFunctionContext> ctx;
|
||||||
|
|
||||||
// Mapping of class name to ClassType
|
|
||||||
// Invariant: users can not create a new class types -> any class types that get deserialized must have been an argument to the type function
|
|
||||||
// Using this invariant, whenever a ClassType is serialized, we can put it into this map
|
|
||||||
// whenever a ClassType is deserialized, we can use this map to return the corresponding value
|
|
||||||
DenseHashMap<std::string, TypeId> classesSerialized{{}};
|
|
||||||
|
|
||||||
// List of errors that occur during serialization/deserialization
|
// List of errors that occur during serialization/deserialization
|
||||||
// At every iteration of serialization/deserialzation, if this list.size() != 0, we halt the process
|
// At every iteration of serialization/deserialization, if this list.size() != 0, we halt the process
|
||||||
std::vector<std::string> errors{};
|
std::vector<std::string> errors{};
|
||||||
|
|
||||||
TypeFunctionRuntimeBuilderState(NotNull<TypeFunctionContext> ctx)
|
TypeFunctionRuntimeBuilderState(NotNull<TypeFunctionContext> ctx)
|
||||||
: ctx(ctx)
|
: ctx(ctx)
|
||||||
, classesSerialized({})
|
|
||||||
, errors({})
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -29,7 +29,7 @@ struct SingletonType;
|
||||||
struct FunctionType;
|
struct FunctionType;
|
||||||
struct TableType;
|
struct TableType;
|
||||||
struct MetatableType;
|
struct MetatableType;
|
||||||
struct ClassType;
|
struct ExternType;
|
||||||
struct AnyType;
|
struct AnyType;
|
||||||
struct UnionType;
|
struct UnionType;
|
||||||
struct IntersectionType;
|
struct IntersectionType;
|
||||||
|
|
|
@ -90,11 +90,11 @@ struct TypeChecker
|
||||||
ControlFlow check(const ScopePtr& scope, TypeId ty, const ScopePtr& funScope, const AstStatLocalFunction& function);
|
ControlFlow check(const ScopePtr& scope, TypeId ty, const ScopePtr& funScope, const AstStatLocalFunction& function);
|
||||||
ControlFlow check(const ScopePtr& scope, const AstStatTypeAlias& typealias);
|
ControlFlow check(const ScopePtr& scope, const AstStatTypeAlias& typealias);
|
||||||
ControlFlow check(const ScopePtr& scope, const AstStatTypeFunction& typefunction);
|
ControlFlow check(const ScopePtr& scope, const AstStatTypeFunction& typefunction);
|
||||||
ControlFlow check(const ScopePtr& scope, const AstStatDeclareClass& declaredClass);
|
ControlFlow check(const ScopePtr& scope, const AstStatDeclareExternType& declaredExternType);
|
||||||
ControlFlow check(const ScopePtr& scope, const AstStatDeclareFunction& declaredFunction);
|
ControlFlow check(const ScopePtr& scope, const AstStatDeclareFunction& declaredFunction);
|
||||||
|
|
||||||
void prototype(const ScopePtr& scope, const AstStatTypeAlias& typealias, int subLevel = 0);
|
void prototype(const ScopePtr& scope, const AstStatTypeAlias& typealias, int subLevel = 0);
|
||||||
void prototype(const ScopePtr& scope, const AstStatDeclareClass& declaredClass);
|
void prototype(const ScopePtr& scope, const AstStatDeclareExternType& declaredExternType);
|
||||||
|
|
||||||
ControlFlow checkBlock(const ScopePtr& scope, const AstStatBlock& statement);
|
ControlFlow checkBlock(const ScopePtr& scope, const AstStatBlock& statement);
|
||||||
ControlFlow checkBlockWithoutRecursionCheck(const ScopePtr& scope, const AstStatBlock& statement);
|
ControlFlow checkBlockWithoutRecursionCheck(const ScopePtr& scope, const AstStatBlock& statement);
|
||||||
|
@ -130,6 +130,7 @@ struct TypeChecker
|
||||||
const PredicateVec& predicates = {}
|
const PredicateVec& predicates = {}
|
||||||
);
|
);
|
||||||
WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprBinary& expr, std::optional<TypeId> expectedType = std::nullopt);
|
WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprBinary& expr, std::optional<TypeId> expectedType = std::nullopt);
|
||||||
|
WithPredicate<TypeId> checkExpr_DEPRECATED(const ScopePtr& scope, const AstExprBinary& expr, std::optional<TypeId> expectedType = std::nullopt);
|
||||||
WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprTypeAssertion& expr);
|
WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprTypeAssertion& expr);
|
||||||
WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprError& expr);
|
WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprError& expr);
|
||||||
WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprIfElse& expr, std::optional<TypeId> expectedType = std::nullopt);
|
WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprIfElse& expr, std::optional<TypeId> expectedType = std::nullopt);
|
||||||
|
@ -399,8 +400,8 @@ private:
|
||||||
const ScopePtr& scope,
|
const ScopePtr& scope,
|
||||||
std::optional<TypeLevel> levelOpt,
|
std::optional<TypeLevel> levelOpt,
|
||||||
const AstNode& node,
|
const AstNode& node,
|
||||||
const AstArray<AstGenericType>& genericNames,
|
const AstArray<AstGenericType*>& genericNames,
|
||||||
const AstArray<AstGenericTypePack>& genericPackNames,
|
const AstArray<AstGenericTypePack*>& genericPackNames,
|
||||||
bool useCache = false
|
bool useCache = false
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -486,7 +487,7 @@ private:
|
||||||
/**
|
/**
|
||||||
* A set of incorrect class definitions which is used to avoid a second-pass analysis.
|
* A set of incorrect class definitions which is used to avoid a second-pass analysis.
|
||||||
*/
|
*/
|
||||||
DenseHashSet<const AstStatDeclareClass*> incorrectClassDefinitions{nullptr};
|
DenseHashSet<const AstStatDeclareExternType*> incorrectExternTypeDefinitions{nullptr};
|
||||||
|
|
||||||
std::vector<std::pair<TypeId, ScopePtr>> deferredQuantification;
|
std::vector<std::pair<TypeId, ScopePtr>> deferredQuantification;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "Luau/Common.h"
|
||||||
|
#include "Luau/NotNull.h"
|
||||||
|
#include "Luau/Polarity.h"
|
||||||
|
#include "Luau/TypeFwd.h"
|
||||||
#include "Luau/Unifiable.h"
|
#include "Luau/Unifiable.h"
|
||||||
#include "Luau/Variant.h"
|
#include "Luau/Variant.h"
|
||||||
#include "Luau/TypeFwd.h"
|
|
||||||
#include "Luau/NotNull.h"
|
|
||||||
#include "Luau/Common.h"
|
|
||||||
|
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <set>
|
#include <set>
|
||||||
|
@ -26,12 +27,14 @@ struct TypeFunctionInstanceTypePack;
|
||||||
struct FreeTypePack
|
struct FreeTypePack
|
||||||
{
|
{
|
||||||
explicit FreeTypePack(TypeLevel level);
|
explicit FreeTypePack(TypeLevel level);
|
||||||
explicit FreeTypePack(Scope* scope);
|
explicit FreeTypePack(Scope* scope, Polarity polarity = Polarity::Unknown);
|
||||||
FreeTypePack(Scope* scope, TypeLevel level);
|
FreeTypePack(Scope* scope, TypeLevel level);
|
||||||
|
|
||||||
int index;
|
int index;
|
||||||
TypeLevel level;
|
TypeLevel level;
|
||||||
Scope* scope = nullptr;
|
Scope* scope = nullptr;
|
||||||
|
|
||||||
|
Polarity polarity = Polarity::Unknown;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct GenericTypePack
|
struct GenericTypePack
|
||||||
|
@ -40,7 +43,7 @@ struct GenericTypePack
|
||||||
GenericTypePack();
|
GenericTypePack();
|
||||||
explicit GenericTypePack(TypeLevel level);
|
explicit GenericTypePack(TypeLevel level);
|
||||||
explicit GenericTypePack(const Name& name);
|
explicit GenericTypePack(const Name& name);
|
||||||
explicit GenericTypePack(Scope* scope);
|
explicit GenericTypePack(Scope* scope, Polarity polarity = Polarity::Unknown);
|
||||||
GenericTypePack(TypeLevel level, const Name& name);
|
GenericTypePack(TypeLevel level, const Name& name);
|
||||||
GenericTypePack(Scope* scope, const Name& name);
|
GenericTypePack(Scope* scope, const Name& name);
|
||||||
|
|
||||||
|
@ -49,10 +52,12 @@ struct GenericTypePack
|
||||||
Scope* scope = nullptr;
|
Scope* scope = nullptr;
|
||||||
Name name;
|
Name name;
|
||||||
bool explicitName = false;
|
bool explicitName = false;
|
||||||
|
|
||||||
|
Polarity polarity = Polarity::Unknown;
|
||||||
};
|
};
|
||||||
|
|
||||||
using BoundTypePack = Unifiable::Bound<TypePackId>;
|
using BoundTypePack = Unifiable::Bound<TypePackId>;
|
||||||
using ErrorTypePack = Unifiable::Error;
|
using ErrorTypePack = Unifiable::Error<TypePackId>;
|
||||||
|
|
||||||
using TypePackVariant =
|
using TypePackVariant =
|
||||||
Unifiable::Variant<TypePackId, FreeTypePack, GenericTypePack, TypePack, VariadicTypePack, BlockedTypePack, TypeFunctionInstanceTypePack>;
|
Unifiable::Variant<TypePackId, FreeTypePack, GenericTypePack, TypePack, VariadicTypePack, BlockedTypePack, TypeFunctionInstanceTypePack>;
|
||||||
|
@ -100,9 +105,9 @@ struct TypeFunctionInstanceTypePack
|
||||||
|
|
||||||
struct TypePackVar
|
struct TypePackVar
|
||||||
{
|
{
|
||||||
explicit TypePackVar(const TypePackVariant& ty);
|
explicit TypePackVar(const TypePackVariant& tp);
|
||||||
explicit TypePackVar(TypePackVariant&& ty);
|
explicit TypePackVar(TypePackVariant&& tp);
|
||||||
TypePackVar(TypePackVariant&& ty, bool persistent);
|
TypePackVar(TypePackVariant&& tp, bool persistent);
|
||||||
|
|
||||||
bool operator==(const TypePackVar& rhs) const;
|
bool operator==(const TypePackVar& rhs) const;
|
||||||
|
|
||||||
|
@ -169,6 +174,7 @@ struct TypePackIterator
|
||||||
|
|
||||||
private:
|
private:
|
||||||
TypePackId currentTypePack = nullptr;
|
TypePackId currentTypePack = nullptr;
|
||||||
|
TypePackId tailCycleCheck = nullptr;
|
||||||
const TypePack* tp = nullptr;
|
const TypePack* tp = nullptr;
|
||||||
size_t currentIndex = 0;
|
size_t currentIndex = 0;
|
||||||
|
|
||||||
|
@ -179,6 +185,8 @@ TypePackIterator begin(TypePackId tp);
|
||||||
TypePackIterator begin(TypePackId tp, const TxnLog* log);
|
TypePackIterator begin(TypePackId tp, const TxnLog* log);
|
||||||
TypePackIterator end(TypePackId tp);
|
TypePackIterator end(TypePackId tp);
|
||||||
|
|
||||||
|
TypePackId getTail(TypePackId tp);
|
||||||
|
|
||||||
using SeenSet = std::set<std::pair<const void*, const void*>>;
|
using SeenSet = std::set<std::pair<const void*, const void*>>;
|
||||||
|
|
||||||
bool areEqual(SeenSet& seen, const TypePackVar& lhs, const TypePackVar& rhs);
|
bool areEqual(SeenSet& seen, const TypePackVar& lhs, const TypePackVar& rhs);
|
||||||
|
|
|
@ -42,9 +42,19 @@ struct Property
|
||||||
/// element.
|
/// element.
|
||||||
struct Index
|
struct Index
|
||||||
{
|
{
|
||||||
|
enum class Variant
|
||||||
|
{
|
||||||
|
Pack,
|
||||||
|
Union,
|
||||||
|
Intersection
|
||||||
|
};
|
||||||
|
|
||||||
/// The 0-based index to use for the lookup.
|
/// The 0-based index to use for the lookup.
|
||||||
size_t index;
|
size_t index;
|
||||||
|
|
||||||
|
/// The sort of thing we're indexing from, this is used in stringifying the type path for errors.
|
||||||
|
Variant variant;
|
||||||
|
|
||||||
bool operator==(const Index& other) const;
|
bool operator==(const Index& other) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -205,6 +215,9 @@ using Path = TypePath::Path;
|
||||||
/// terribly clear to end users of the Luau type system.
|
/// terribly clear to end users of the Luau type system.
|
||||||
std::string toString(const TypePath::Path& path, bool prefixDot = false);
|
std::string toString(const TypePath::Path& path, bool prefixDot = false);
|
||||||
|
|
||||||
|
/// Converts a Path to a human readable string for error reporting.
|
||||||
|
std::string toStringHuman(const TypePath::Path& path);
|
||||||
|
|
||||||
std::optional<TypeOrPack> traverse(TypeId root, const Path& path, NotNull<BuiltinTypes> builtinTypes);
|
std::optional<TypeOrPack> traverse(TypeId root, const Path& path, NotNull<BuiltinTypes> builtinTypes);
|
||||||
std::optional<TypeOrPack> traverse(TypePackId root, const Path& path, NotNull<BuiltinTypes> builtinTypes);
|
std::optional<TypeOrPack> traverse(TypePackId root, const Path& path, NotNull<BuiltinTypes> builtinTypes);
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,7 @@ struct InConditionalContext
|
||||||
TypeContext* typeContext;
|
TypeContext* typeContext;
|
||||||
TypeContext oldValue;
|
TypeContext oldValue;
|
||||||
|
|
||||||
InConditionalContext(TypeContext* c)
|
explicit InConditionalContext(TypeContext* c)
|
||||||
: typeContext(c)
|
: typeContext(c)
|
||||||
, oldValue(*c)
|
, oldValue(*c)
|
||||||
{
|
{
|
||||||
|
@ -269,8 +269,8 @@ bool isLiteral(const AstExpr* expr);
|
||||||
std::vector<TypeId> findBlockedTypesIn(AstExprTable* expr, NotNull<DenseHashMap<const AstExpr*, TypeId>> astTypes);
|
std::vector<TypeId> findBlockedTypesIn(AstExprTable* expr, NotNull<DenseHashMap<const AstExpr*, TypeId>> astTypes);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a function call and a mapping from expression to type, determine
|
* 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.
|
* 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
|
* This is used as a precondition for bidirectional inference: be warned that
|
||||||
* the behavior of this algorithm is tightly coupled to that of bidirectional
|
* the behavior of this algorithm is tightly coupled to that of bidirectional
|
||||||
* inference.
|
* 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);
|
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
|
} // namespace Luau
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
#include "Luau/Variant.h"
|
#include "Luau/Variant.h"
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
|
@ -94,19 +95,29 @@ struct Bound
|
||||||
Id boundTo;
|
Id boundTo;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template<typename Id>
|
||||||
struct Error
|
struct Error
|
||||||
{
|
{
|
||||||
// This constructor has to be public, since it's used in Type and TypePack,
|
// This constructor has to be public, since it's used in Type and TypePack,
|
||||||
// but shouldn't be called directly. Please use errorRecoveryType() instead.
|
// but shouldn't be called directly. Please use errorRecoveryType() instead.
|
||||||
Error();
|
explicit Error();
|
||||||
|
|
||||||
|
explicit Error(Id synthetic)
|
||||||
|
: synthetic{synthetic}
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
int index;
|
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:
|
private:
|
||||||
static int nextIndex;
|
static int nextIndex;
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename Id, typename... Value>
|
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
|
} // namespace Luau::Unifiable
|
||||||
|
|
|
@ -93,10 +93,6 @@ struct Unifier
|
||||||
|
|
||||||
Unifier(NotNull<Normalizer> normalizer, NotNull<Scope> scope, const Location& location, Variance variance, TxnLog* parentLog = nullptr);
|
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.
|
// Test whether the two type vars unify. Never commits the result.
|
||||||
ErrorVec canUnify(TypeId subTy, TypeId superTy);
|
ErrorVec canUnify(TypeId subTy, TypeId superTy);
|
||||||
ErrorVec canUnify(TypePackId subTy, TypePackId superTy, bool isFunctionCall = false);
|
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 tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection = false, const LiteralProperties* aliasableMap = nullptr);
|
||||||
void tryUnifyScalarShape(TypeId subTy, TypeId superTy, bool reversed);
|
void tryUnifyScalarShape(TypeId subTy, TypeId superTy, bool reversed);
|
||||||
void tryUnifyWithMetatable(TypeId subTy, TypeId superTy, bool reversed);
|
void tryUnifyWithMetatable(TypeId subTy, TypeId superTy, bool reversed);
|
||||||
void tryUnifyWithClass(TypeId subTy, TypeId superTy, bool reversed);
|
void tryUnifyWithExternType(TypeId subTy, TypeId superTy, bool reversed);
|
||||||
void tryUnifyNegations(TypeId subTy, TypeId superTy);
|
void tryUnifyNegations(TypeId subTy, TypeId superTy);
|
||||||
|
|
||||||
TypePackId tryApplyOverloadedFunction(TypeId function, const NormalizedFunctionType& overloads, TypePackId args);
|
TypePackId tryApplyOverloadedFunction(TypeId function, const NormalizedFunctionType& overloads, TypePackId args);
|
||||||
|
@ -169,7 +165,6 @@ private:
|
||||||
|
|
||||||
std::optional<TypeId> findTablePropertyRespectingMeta(TypeId lhsType, Name name);
|
std::optional<TypeId> findTablePropertyRespectingMeta(TypeId lhsType, Name name);
|
||||||
|
|
||||||
TxnLog combineLogsIntoIntersection(std::vector<TxnLog> logs);
|
|
||||||
TxnLog combineLogsIntoUnion(std::vector<TxnLog> logs);
|
TxnLog combineLogsIntoUnion(std::vector<TxnLog> logs);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
@ -195,11 +190,6 @@ private:
|
||||||
|
|
||||||
// Available after regular type pack unification errors
|
// Available after regular type pack unification errors
|
||||||
std::optional<int> firstPackErrorPos;
|
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);
|
void promoteTypeLevels(TxnLog& log, const TypeArena* arena, TypeLevel minLevel, Scope* outerScope, bool useScope, TypePackId tp);
|
||||||
|
|
|
@ -44,6 +44,12 @@ struct Unifier2
|
||||||
// Mapping from generic type packs to `TypePack`s of free types to be used in instantiation.
|
// Mapping from generic type packs to `TypePack`s of free types to be used in instantiation.
|
||||||
DenseHashMap<TypePackId, TypePackId> genericPackSubstitutions{nullptr};
|
DenseHashMap<TypePackId, TypePackId> genericPackSubstitutions{nullptr};
|
||||||
|
|
||||||
|
// Unification sometimes results in the creation of new free types.
|
||||||
|
// We collect them here so that other systems can perform necessary
|
||||||
|
// bookkeeping.
|
||||||
|
std::vector<TypeId> newFreshTypes;
|
||||||
|
std::vector<TypePackId> newFreshTypePacks;
|
||||||
|
|
||||||
int recursionCount = 0;
|
int recursionCount = 0;
|
||||||
int recursionLimit = 0;
|
int recursionLimit = 0;
|
||||||
|
|
||||||
|
@ -87,6 +93,9 @@ struct Unifier2
|
||||||
bool unify(const AnyType* subAny, const TableType* superTable);
|
bool unify(const AnyType* subAny, const TableType* superTable);
|
||||||
bool unify(const TableType* subTable, const AnyType* superAny);
|
bool unify(const TableType* subTable, const AnyType* superAny);
|
||||||
|
|
||||||
|
bool unify(const MetatableType* subMetatable, const AnyType*);
|
||||||
|
bool unify(const AnyType*, const MetatableType* superMetatable);
|
||||||
|
|
||||||
// TODO think about this one carefully. We don't do unions or intersections of type packs
|
// TODO think about this one carefully. We don't do unions or intersections of type packs
|
||||||
bool unify(TypePackId subTp, TypePackId superTp);
|
bool unify(TypePackId subTp, TypePackId superTp);
|
||||||
|
|
||||||
|
@ -110,6 +119,9 @@ private:
|
||||||
// Returns true if needle occurs within haystack already. ie if we bound
|
// Returns true if needle occurs within haystack already. ie if we bound
|
||||||
// needle to haystack, would a cyclic TypePack result?
|
// needle to haystack, would a cyclic TypePack result?
|
||||||
OccursCheckResult occursCheck(DenseHashSet<TypePackId>& seen, TypePackId needle, TypePackId haystack);
|
OccursCheckResult occursCheck(DenseHashSet<TypePackId>& seen, TypePackId needle, TypePackId haystack);
|
||||||
|
|
||||||
|
TypeId freshType(NotNull<Scope> scope, Polarity polarity);
|
||||||
|
TypePackId freshTypePack(NotNull<Scope> scope, Polarity polarity);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Luau
|
} // namespace Luau
|
||||||
|
|
|
@ -49,6 +49,26 @@ struct UnifierSharedState
|
||||||
DenseHashSet<TypePackId> tempSeenTp{nullptr};
|
DenseHashSet<TypePackId> tempSeenTp{nullptr};
|
||||||
|
|
||||||
UnifierCounters counters;
|
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
|
} // namespace Luau
|
||||||
|
|
|
@ -10,7 +10,6 @@
|
||||||
#include "Type.h"
|
#include "Type.h"
|
||||||
|
|
||||||
LUAU_FASTINT(LuauVisitRecursionLimit)
|
LUAU_FASTINT(LuauVisitRecursionLimit)
|
||||||
LUAU_FASTFLAG(LuauBoundLazyTypes2)
|
|
||||||
LUAU_FASTFLAG(LuauSolverV2)
|
LUAU_FASTFLAG(LuauSolverV2)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
|
@ -86,6 +85,8 @@ struct GenericTypeVisitor
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
virtual ~GenericTypeVisitor() {}
|
||||||
|
|
||||||
virtual void cycle(TypeId) {}
|
virtual void cycle(TypeId) {}
|
||||||
virtual void cycle(TypePackId) {}
|
virtual void cycle(TypePackId) {}
|
||||||
|
|
||||||
|
@ -125,7 +126,7 @@ struct GenericTypeVisitor
|
||||||
{
|
{
|
||||||
return visit(ty);
|
return visit(ty);
|
||||||
}
|
}
|
||||||
virtual bool visit(TypeId ty, const ClassType& ctv)
|
virtual bool visit(TypeId ty, const ExternType& etv)
|
||||||
{
|
{
|
||||||
return visit(ty);
|
return visit(ty);
|
||||||
}
|
}
|
||||||
|
@ -190,7 +191,7 @@ struct GenericTypeVisitor
|
||||||
{
|
{
|
||||||
return visit(tp);
|
return visit(tp);
|
||||||
}
|
}
|
||||||
virtual bool visit(TypePackId tp, const Unifiable::Error& etp)
|
virtual bool visit(TypePackId tp, const ErrorTypePack& etp)
|
||||||
{
|
{
|
||||||
return visit(tp);
|
return visit(tp);
|
||||||
}
|
}
|
||||||
|
@ -312,11 +313,11 @@ struct GenericTypeVisitor
|
||||||
traverse(mtv->metatable);
|
traverse(mtv->metatable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (auto ctv = get<ClassType>(ty))
|
else if (auto etv = get<ExternType>(ty))
|
||||||
{
|
{
|
||||||
if (visit(ty, *ctv))
|
if (visit(ty, *etv))
|
||||||
{
|
{
|
||||||
for (const auto& [name, prop] : ctv->props)
|
for (const auto& [name, prop] : etv->props)
|
||||||
{
|
{
|
||||||
if (FFlag::LuauSolverV2)
|
if (FFlag::LuauSolverV2)
|
||||||
{
|
{
|
||||||
|
@ -334,16 +335,16 @@ struct GenericTypeVisitor
|
||||||
traverse(prop.type());
|
traverse(prop.type());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ctv->parent)
|
if (etv->parent)
|
||||||
traverse(*ctv->parent);
|
traverse(*etv->parent);
|
||||||
|
|
||||||
if (ctv->metatable)
|
if (etv->metatable)
|
||||||
traverse(*ctv->metatable);
|
traverse(*etv->metatable);
|
||||||
|
|
||||||
if (ctv->indexer)
|
if (etv->indexer)
|
||||||
{
|
{
|
||||||
traverse(ctv->indexer->indexType);
|
traverse(etv->indexer->indexType);
|
||||||
traverse(ctv->indexer->indexResultType);
|
traverse(etv->indexer->indexResultType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -395,7 +396,7 @@ struct GenericTypeVisitor
|
||||||
traverse(unwrapped);
|
traverse(unwrapped);
|
||||||
|
|
||||||
// Visiting into LazyType that hasn't been unwrapped may necessarily cause infinite expansion, so we don't do that on purpose.
|
// Visiting into LazyType that hasn't been unwrapped may necessarily cause infinite expansion, so we don't do that on purpose.
|
||||||
// Asserting also makes no sense, because the type _will_ happen here, most likely as a property of some ClassType
|
// Asserting also makes no sense, because the type _will_ happen here, most likely as a property of some ExternType
|
||||||
// that doesn't need to be expanded.
|
// that doesn't need to be expanded.
|
||||||
}
|
}
|
||||||
else if (auto stv = get<SingletonType>(ty))
|
else if (auto stv = get<SingletonType>(ty))
|
||||||
|
@ -461,7 +462,7 @@ struct GenericTypeVisitor
|
||||||
else if (auto gtv = get<GenericTypePack>(tp))
|
else if (auto gtv = get<GenericTypePack>(tp))
|
||||||
visit(tp, *gtv);
|
visit(tp, *gtv);
|
||||||
|
|
||||||
else if (auto etv = get<Unifiable::Error>(tp))
|
else if (auto etv = get<ErrorTypePack>(tp))
|
||||||
visit(tp, *etv);
|
visit(tp, *etv);
|
||||||
|
|
||||||
else if (auto pack = get<TypePack>(tp))
|
else if (auto pack = get<TypePack>(tp))
|
||||||
|
|
|
@ -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
|
|
|
@ -88,7 +88,7 @@ TypePackId Anyification::clean(TypePackId tp)
|
||||||
|
|
||||||
bool Anyification::ignoreChildren(TypeId ty)
|
bool Anyification::ignoreChildren(TypeId ty)
|
||||||
{
|
{
|
||||||
if (get<ClassType>(ty))
|
if (get<ExternType>(ty))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
return ty->persistent;
|
return ty->persistent;
|
||||||
|
|
|
@ -31,7 +31,7 @@ bool ApplyTypeFunction::ignoreChildren(TypeId ty)
|
||||||
{
|
{
|
||||||
if (get<GenericType>(ty))
|
if (get<GenericType>(ty))
|
||||||
return true;
|
return true;
|
||||||
else if (get<ClassType>(ty))
|
else if (get<ExternType>(ty))
|
||||||
return true;
|
return true;
|
||||||
else
|
else
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -8,6 +8,8 @@
|
||||||
|
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
|
||||||
|
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
|
||||||
|
@ -431,8 +433,16 @@ struct AstJsonEncoder : public AstVisitor
|
||||||
if (node->self)
|
if (node->self)
|
||||||
PROP(self);
|
PROP(self);
|
||||||
PROP(args);
|
PROP(args);
|
||||||
if (node->returnAnnotation)
|
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
|
||||||
PROP(returnAnnotation);
|
{
|
||||||
|
if (node->returnAnnotation)
|
||||||
|
PROP(returnAnnotation);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (node->returnAnnotation_DEPRECATED)
|
||||||
|
write("returnAnnotation", node->returnAnnotation_DEPRECATED);
|
||||||
|
}
|
||||||
PROP(vararg);
|
PROP(vararg);
|
||||||
PROP(varargLocation);
|
PROP(varargLocation);
|
||||||
if (node->varargAnnotation)
|
if (node->varargAnnotation)
|
||||||
|
@ -465,26 +475,26 @@ struct AstJsonEncoder : public AstVisitor
|
||||||
writeRaw("}");
|
writeRaw("}");
|
||||||
}
|
}
|
||||||
|
|
||||||
void write(const AstGenericType& genericType)
|
void write(class AstGenericType* genericType)
|
||||||
{
|
{
|
||||||
writeRaw("{");
|
writeRaw("{");
|
||||||
bool c = pushComma();
|
bool c = pushComma();
|
||||||
writeType("AstGenericType");
|
writeType("AstGenericType");
|
||||||
write("name", genericType.name);
|
write("name", genericType->name);
|
||||||
if (genericType.defaultValue)
|
if (genericType->defaultValue)
|
||||||
write("luauType", genericType.defaultValue);
|
write("luauType", genericType->defaultValue);
|
||||||
popComma(c);
|
popComma(c);
|
||||||
writeRaw("}");
|
writeRaw("}");
|
||||||
}
|
}
|
||||||
|
|
||||||
void write(const AstGenericTypePack& genericTypePack)
|
void write(class AstGenericTypePack* genericTypePack)
|
||||||
{
|
{
|
||||||
writeRaw("{");
|
writeRaw("{");
|
||||||
bool c = pushComma();
|
bool c = pushComma();
|
||||||
writeType("AstGenericTypePack");
|
writeType("AstGenericTypePack");
|
||||||
write("name", genericTypePack.name);
|
write("name", genericTypePack->name);
|
||||||
if (genericTypePack.defaultValue)
|
if (genericTypePack->defaultValue)
|
||||||
write("luauType", genericTypePack.defaultValue);
|
write("luauType", genericTypePack->defaultValue);
|
||||||
popComma(c);
|
popComma(c);
|
||||||
writeRaw("}");
|
writeRaw("}");
|
||||||
}
|
}
|
||||||
|
@ -902,7 +912,10 @@ struct AstJsonEncoder : public AstVisitor
|
||||||
PROP(paramNames);
|
PROP(paramNames);
|
||||||
PROP(vararg);
|
PROP(vararg);
|
||||||
PROP(varargLocation);
|
PROP(varargLocation);
|
||||||
PROP(retTypes);
|
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
|
||||||
|
PROP(retTypes);
|
||||||
|
else
|
||||||
|
write("retTypes", node->retTypes_DEPRECATED);
|
||||||
PROP(generics);
|
PROP(generics);
|
||||||
PROP(genericPacks);
|
PROP(genericPacks);
|
||||||
}
|
}
|
||||||
|
@ -923,7 +936,7 @@ struct AstJsonEncoder : public AstVisitor
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void write(const AstDeclaredClassProp& prop)
|
void write(const AstDeclaredExternTypeProperty& prop)
|
||||||
{
|
{
|
||||||
writeRaw("{");
|
writeRaw("{");
|
||||||
bool c = pushComma();
|
bool c = pushComma();
|
||||||
|
@ -936,7 +949,7 @@ struct AstJsonEncoder : public AstVisitor
|
||||||
writeRaw("}");
|
writeRaw("}");
|
||||||
}
|
}
|
||||||
|
|
||||||
void write(class AstStatDeclareClass* node)
|
void write(class AstStatDeclareExternType* node)
|
||||||
{
|
{
|
||||||
writeNode(
|
writeNode(
|
||||||
node,
|
node,
|
||||||
|
@ -1048,7 +1061,10 @@ struct AstJsonEncoder : public AstVisitor
|
||||||
PROP(genericPacks);
|
PROP(genericPacks);
|
||||||
PROP(argTypes);
|
PROP(argTypes);
|
||||||
PROP(argNames);
|
PROP(argNames);
|
||||||
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)
|
void write(class AstTypeUnion* node)
|
||||||
{
|
{
|
||||||
writeNode(
|
writeNode(
|
||||||
|
@ -1146,6 +1167,8 @@ struct AstJsonEncoder : public AstVisitor
|
||||||
return writeString("checked");
|
return writeString("checked");
|
||||||
case AstAttr::Type::Native:
|
case AstAttr::Type::Native:
|
||||||
return writeString("native");
|
return writeString("native");
|
||||||
|
case AstAttr::Type::Deprecated:
|
||||||
|
return writeString("deprecated");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
bool visit(class AstTypeSingletonBool* node) override
|
||||||
{
|
{
|
||||||
writeNode(
|
writeNode(
|
||||||
|
@ -1409,7 +1445,7 @@ struct AstJsonEncoder : public AstVisitor
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool visit(class AstStatDeclareClass* node) override
|
bool visit(class AstStatDeclareExternType* node) override
|
||||||
{
|
{
|
||||||
write(node);
|
write(node);
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -13,8 +13,6 @@
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauSolverV2)
|
LUAU_FASTFLAG(LuauSolverV2)
|
||||||
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauDocumentationAtPosition)
|
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
|
||||||
|
@ -43,11 +41,15 @@ struct AutocompleteNodeFinder : public AstVisitor
|
||||||
|
|
||||||
bool visit(AstStat* stat) override
|
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);
|
ancestry.push_back(stat);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -518,7 +520,6 @@ static std::optional<DocumentationSymbol> getMetatableDocumentation(
|
||||||
const AstName& index
|
const AstName& index
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(FFlag::LuauDocumentationAtPosition);
|
|
||||||
auto indexIt = mtable->props.find("__index");
|
auto indexIt = mtable->props.find("__index");
|
||||||
if (indexIt == mtable->props.end())
|
if (indexIt == mtable->props.end())
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
|
@ -573,30 +574,11 @@ std::optional<DocumentationSymbol> getDocumentationSymbolAtPosition(const Source
|
||||||
return checkOverloadedDocumentationSymbol(module, propIt->second.type(), parentExpr, propIt->second.documentationSymbol);
|
return checkOverloadedDocumentationSymbol(module, propIt->second.type(), parentExpr, propIt->second.documentationSymbol);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (const ClassType* ctv = get<ClassType>(parentTy))
|
else if (const ExternType* etv = get<ExternType>(parentTy))
|
||||||
{
|
{
|
||||||
if (FFlag::LuauDocumentationAtPosition)
|
while (etv)
|
||||||
{
|
{
|
||||||
while (ctv)
|
if (auto propIt = etv->props.find(indexName->index.value); propIt != etv->props.end())
|
||||||
{
|
|
||||||
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 (FFlag::LuauSolverV2)
|
if (FFlag::LuauSolverV2)
|
||||||
{
|
{
|
||||||
|
@ -608,17 +590,15 @@ std::optional<DocumentationSymbol> getDocumentationSymbolAtPosition(const Source
|
||||||
module, propIt->second.type(), parentExpr, propIt->second.documentationSymbol
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
#include "Luau/Autocomplete.h"
|
#include "Luau/Autocomplete.h"
|
||||||
|
|
||||||
#include "Luau/AstQuery.h"
|
#include "Luau/AstQuery.h"
|
||||||
|
#include "Luau/TimeTrace.h"
|
||||||
#include "Luau/TypeArena.h"
|
#include "Luau/TypeArena.h"
|
||||||
#include "Luau/Module.h"
|
#include "Luau/Module.h"
|
||||||
#include "Luau/Frontend.h"
|
#include "Luau/Frontend.h"
|
||||||
|
@ -15,6 +16,9 @@ namespace Luau
|
||||||
|
|
||||||
AutocompleteResult autocomplete(Frontend& frontend, const ModuleName& moduleName, Position position, StringCompletionCallback callback)
|
AutocompleteResult autocomplete(Frontend& frontend, const ModuleName& moduleName, Position position, StringCompletionCallback callback)
|
||||||
{
|
{
|
||||||
|
LUAU_TIMETRACE_SCOPE("Luau::autocomplete", "Autocomplete");
|
||||||
|
LUAU_TIMETRACE_ARGUMENT("name", moduleName.c_str());
|
||||||
|
|
||||||
const SourceModule* sourceModule = frontend.getSourceModule(moduleName);
|
const SourceModule* sourceModule = frontend.getSourceModule(moduleName);
|
||||||
if (!sourceModule)
|
if (!sourceModule)
|
||||||
return {};
|
return {};
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
#include "Luau/Common.h"
|
#include "Luau/Common.h"
|
||||||
#include "Luau/FileResolver.h"
|
#include "Luau/FileResolver.h"
|
||||||
#include "Luau/Frontend.h"
|
#include "Luau/Frontend.h"
|
||||||
|
#include "Luau/TimeTrace.h"
|
||||||
#include "Luau/ToString.h"
|
#include "Luau/ToString.h"
|
||||||
#include "Luau/Subtyping.h"
|
#include "Luau/Subtyping.h"
|
||||||
#include "Luau/TypeInfer.h"
|
#include "Luau/TypeInfer.h"
|
||||||
|
@ -20,12 +21,13 @@
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauSolverV2)
|
LUAU_FASTFLAG(LuauSolverV2)
|
||||||
LUAU_FASTFLAGVARIABLE(AutocompleteRequirePathSuggestions2)
|
|
||||||
LUAU_DYNAMIC_FASTINT(LuauTypeSolverRelease)
|
|
||||||
LUAU_FASTINT(LuauTypeInferIterationLimit)
|
LUAU_FASTINT(LuauTypeInferIterationLimit)
|
||||||
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
||||||
|
LUAU_FASTFLAGVARIABLE(DebugLuauMagicVariableNames)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauAutocompleteRefactorsForIncrementalAutocomplete)
|
LUAU_FASTFLAGVARIABLE(LuauAutocompleteUsesModuleForTypeCompatibility)
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauAutocompleteUnionCopyPreviousSeen)
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauAutocompleteMissingFollows)
|
||||||
|
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
|
||||||
|
|
||||||
static const std::unordered_set<std::string> kStatementStartingKeywords =
|
static const std::unordered_set<std::string> kStatementStartingKeywords =
|
||||||
{"while", "if", "local", "repeat", "function", "do", "for", "return", "break", "continue", "type", "export"};
|
{"while", "if", "local", "repeat", "function", "do", "for", "return", "break", "continue", "type", "export"};
|
||||||
|
@ -78,6 +80,8 @@ static ParenthesesRecommendation getParenRecommendationForIntersect(const Inters
|
||||||
ParenthesesRecommendation rec = ParenthesesRecommendation::None;
|
ParenthesesRecommendation rec = ParenthesesRecommendation::None;
|
||||||
for (Luau::TypeId partId : intersect->parts)
|
for (Luau::TypeId partId : intersect->parts)
|
||||||
{
|
{
|
||||||
|
if (FFlag::LuauAutocompleteMissingFollows)
|
||||||
|
partId = follow(partId);
|
||||||
if (auto partFunc = Luau::get<FunctionType>(partId))
|
if (auto partFunc = Luau::get<FunctionType>(partId))
|
||||||
{
|
{
|
||||||
rec = std::max(rec, getParenRecommendationForFunc(partFunc, nodes));
|
rec = std::max(rec, getParenRecommendationForFunc(partFunc, nodes));
|
||||||
|
@ -147,35 +151,91 @@ static std::optional<TypeId> findExpectedTypeAt(const Module& module, AstNode* n
|
||||||
return *it;
|
return *it;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool checkTypeMatch(TypeId subTy, TypeId superTy, NotNull<Scope> scope, TypeArena* typeArena, NotNull<BuiltinTypes> builtinTypes)
|
static bool checkTypeMatch(
|
||||||
|
const Module& module,
|
||||||
|
TypeId subTy,
|
||||||
|
TypeId superTy,
|
||||||
|
NotNull<Scope> scope,
|
||||||
|
TypeArena* typeArena,
|
||||||
|
NotNull<BuiltinTypes> builtinTypes
|
||||||
|
)
|
||||||
{
|
{
|
||||||
InternalErrorReporter iceReporter;
|
InternalErrorReporter iceReporter;
|
||||||
UnifierSharedState unifierState(&iceReporter);
|
UnifierSharedState unifierState(&iceReporter);
|
||||||
|
SimplifierPtr simplifier = newSimplifier(NotNull{typeArena}, builtinTypes);
|
||||||
Normalizer normalizer{typeArena, builtinTypes, NotNull{&unifierState}};
|
Normalizer normalizer{typeArena, builtinTypes, NotNull{&unifierState}};
|
||||||
|
if (FFlag::LuauAutocompleteUsesModuleForTypeCompatibility)
|
||||||
if (FFlag::LuauSolverV2)
|
|
||||||
{
|
{
|
||||||
TypeCheckLimits limits;
|
if (module.checkedInNewSolver)
|
||||||
TypeFunctionRuntime typeFunctionRuntime{
|
{
|
||||||
NotNull{&iceReporter}, NotNull{&limits}
|
TypeCheckLimits limits;
|
||||||
}; // TODO: maybe subtyping checks should not invoke user-defined type function runtime
|
TypeFunctionRuntime typeFunctionRuntime{
|
||||||
|
NotNull{&iceReporter}, NotNull{&limits}
|
||||||
|
}; // TODO: maybe subtyping checks should not invoke user-defined type function runtime
|
||||||
|
|
||||||
unifierState.counters.recursionLimit = FInt::LuauTypeInferRecursionLimit;
|
unifierState.counters.recursionLimit = FInt::LuauTypeInferRecursionLimit;
|
||||||
unifierState.counters.iterationLimit = FInt::LuauTypeInferIterationLimit;
|
unifierState.counters.iterationLimit = FInt::LuauTypeInferIterationLimit;
|
||||||
|
|
||||||
Subtyping subtyping{builtinTypes, NotNull{typeArena}, NotNull{&normalizer}, NotNull{&typeFunctionRuntime}, NotNull{&iceReporter}};
|
Subtyping subtyping{
|
||||||
|
builtinTypes,
|
||||||
|
NotNull{typeArena},
|
||||||
|
NotNull{simplifier.get()},
|
||||||
|
NotNull{&normalizer},
|
||||||
|
NotNull{&typeFunctionRuntime},
|
||||||
|
NotNull{&iceReporter}
|
||||||
|
};
|
||||||
|
|
||||||
return subtyping.isSubtype(subTy, superTy, scope).isSubtype;
|
return subtyping.isSubtype(subTy, superTy, scope).isSubtype;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Unifier unifier(NotNull<Normalizer>{&normalizer}, scope, Location(), Variance::Covariant);
|
||||||
|
|
||||||
|
// Cost of normalization can be too high for autocomplete response time requirements
|
||||||
|
unifier.normalize = false;
|
||||||
|
unifier.checkInhabited = false;
|
||||||
|
|
||||||
|
unifierState.counters.recursionLimit = FInt::LuauTypeInferRecursionLimit;
|
||||||
|
unifierState.counters.iterationLimit = FInt::LuauTypeInferIterationLimit;
|
||||||
|
|
||||||
|
return unifier.canUnify(subTy, superTy).empty();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Unifier unifier(NotNull<Normalizer>{&normalizer}, scope, Location(), Variance::Covariant);
|
if (FFlag::LuauSolverV2)
|
||||||
|
{
|
||||||
|
TypeCheckLimits limits;
|
||||||
|
TypeFunctionRuntime typeFunctionRuntime{
|
||||||
|
NotNull{&iceReporter}, NotNull{&limits}
|
||||||
|
}; // TODO: maybe subtyping checks should not invoke user-defined type function runtime
|
||||||
|
|
||||||
// Cost of normalization can be too high for autocomplete response time requirements
|
unifierState.counters.recursionLimit = FInt::LuauTypeInferRecursionLimit;
|
||||||
unifier.normalize = false;
|
unifierState.counters.iterationLimit = FInt::LuauTypeInferIterationLimit;
|
||||||
unifier.checkInhabited = false;
|
|
||||||
|
|
||||||
return unifier.canUnify(subTy, superTy).empty();
|
Subtyping subtyping{
|
||||||
|
builtinTypes,
|
||||||
|
NotNull{typeArena},
|
||||||
|
NotNull{simplifier.get()},
|
||||||
|
NotNull{&normalizer},
|
||||||
|
NotNull{&typeFunctionRuntime},
|
||||||
|
NotNull{&iceReporter}
|
||||||
|
};
|
||||||
|
|
||||||
|
return subtyping.isSubtype(subTy, superTy, scope).isSubtype;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Unifier unifier(NotNull<Normalizer>{&normalizer}, scope, Location(), Variance::Covariant);
|
||||||
|
|
||||||
|
// Cost of normalization can be too high for autocomplete response time requirements
|
||||||
|
unifier.normalize = false;
|
||||||
|
unifier.checkInhabited = false;
|
||||||
|
unifierState.counters.recursionLimit = FInt::LuauTypeInferRecursionLimit;
|
||||||
|
unifierState.counters.iterationLimit = FInt::LuauTypeInferIterationLimit;
|
||||||
|
|
||||||
|
return unifier.canUnify(subTy, superTy).empty();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -201,10 +261,10 @@ static TypeCorrectKind checkTypeCorrectKind(
|
||||||
|
|
||||||
TypeId expectedType = follow(*typeAtPosition);
|
TypeId expectedType = follow(*typeAtPosition);
|
||||||
|
|
||||||
auto checkFunctionType = [typeArena, builtinTypes, moduleScope, &expectedType](const FunctionType* ftv)
|
auto checkFunctionType = [typeArena, builtinTypes, moduleScope, &expectedType, &module](const FunctionType* ftv)
|
||||||
{
|
{
|
||||||
if (std::optional<TypeId> firstRetTy = first(ftv->retTypes))
|
if (std::optional<TypeId> firstRetTy = first(ftv->retTypes))
|
||||||
return checkTypeMatch(*firstRetTy, expectedType, moduleScope, typeArena, builtinTypes);
|
return checkTypeMatch(module, *firstRetTy, expectedType, moduleScope, typeArena, builtinTypes);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
@ -227,7 +287,7 @@ static TypeCorrectKind checkTypeCorrectKind(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return checkTypeMatch(ty, expectedType, moduleScope, typeArena, builtinTypes) ? TypeCorrectKind::Correct : TypeCorrectKind::None;
|
return checkTypeMatch(module, ty, expectedType, moduleScope, typeArena, builtinTypes) ? TypeCorrectKind::Correct : TypeCorrectKind::None;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class PropIndexType
|
enum class PropIndexType
|
||||||
|
@ -247,7 +307,7 @@ static void autocompleteProps(
|
||||||
const std::vector<AstNode*>& nodes,
|
const std::vector<AstNode*>& nodes,
|
||||||
AutocompleteEntryMap& result,
|
AutocompleteEntryMap& result,
|
||||||
std::unordered_set<TypeId>& seen,
|
std::unordered_set<TypeId>& seen,
|
||||||
std::optional<const ClassType*> containingClass = std::nullopt
|
std::optional<const ExternType*> containingExternType = std::nullopt
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
rootTy = follow(rootTy);
|
rootTy = follow(rootTy);
|
||||||
|
@ -270,15 +330,15 @@ static void autocompleteProps(
|
||||||
if (calledWithSelf == ftv->hasSelf)
|
if (calledWithSelf == ftv->hasSelf)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
// Calls on classes require strict match between how function is declared and how it's called
|
// Calls on extern types require strict match between how function is declared and how it's called
|
||||||
if (get<ClassType>(rootTy))
|
if (get<ExternType>(rootTy))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// When called with ':', but declared without 'self', it is invalid if a function has incompatible first argument or no arguments at all
|
// When called with ':', but declared without 'self', it is invalid if a function has incompatible first argument or no arguments at all
|
||||||
// When called with '.', but declared with 'self', it is considered invalid if first argument is compatible
|
// When called with '.', but declared with 'self', it is considered invalid if first argument is compatible
|
||||||
if (std::optional<TypeId> firstArgTy = first(ftv->argTypes))
|
if (std::optional<TypeId> firstArgTy = first(ftv->argTypes))
|
||||||
{
|
{
|
||||||
if (checkTypeMatch(rootTy, *firstArgTy, NotNull{module.getModuleScope().get()}, typeArena, builtinTypes))
|
if (checkTypeMatch(module, rootTy, *firstArgTy, NotNull{module.getModuleScope().get()}, typeArena, builtinTypes))
|
||||||
return calledWithSelf;
|
return calledWithSelf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -304,7 +364,7 @@ static void autocompleteProps(
|
||||||
return calledWithSelf;
|
return calledWithSelf;
|
||||||
};
|
};
|
||||||
|
|
||||||
auto fillProps = [&](const ClassType::Props& props)
|
auto fillProps = [&](const ExternType::Props& props)
|
||||||
{
|
{
|
||||||
for (const auto& [name, prop] : props)
|
for (const auto& [name, prop] : props)
|
||||||
{
|
{
|
||||||
|
@ -337,7 +397,7 @@ static void autocompleteProps(
|
||||||
prop.deprecated,
|
prop.deprecated,
|
||||||
isWrongIndexer(type),
|
isWrongIndexer(type),
|
||||||
typeCorrect,
|
typeCorrect,
|
||||||
containingClass,
|
containingExternType,
|
||||||
&prop,
|
&prop,
|
||||||
prop.documentationSymbol,
|
prop.documentationSymbol,
|
||||||
{},
|
{},
|
||||||
|
@ -368,12 +428,12 @@ static void autocompleteProps(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (auto cls = get<ClassType>(ty))
|
if (auto cls = get<ExternType>(ty))
|
||||||
{
|
{
|
||||||
containingClass = containingClass.value_or(cls);
|
containingExternType = containingExternType.value_or(cls);
|
||||||
fillProps(cls->props);
|
fillProps(cls->props);
|
||||||
if (cls->parent)
|
if (cls->parent)
|
||||||
autocompleteProps(module, typeArena, builtinTypes, rootTy, *cls->parent, indexType, nodes, result, seen, containingClass);
|
autocompleteProps(module, typeArena, builtinTypes, rootTy, *cls->parent, indexType, nodes, result, seen, containingExternType);
|
||||||
}
|
}
|
||||||
else if (auto tbl = get<TableType>(ty))
|
else if (auto tbl = get<TableType>(ty))
|
||||||
fillProps(tbl->props);
|
fillProps(tbl->props);
|
||||||
|
@ -424,6 +484,21 @@ static void autocompleteProps(
|
||||||
AutocompleteEntryMap inner;
|
AutocompleteEntryMap inner;
|
||||||
std::unordered_set<TypeId> innerSeen;
|
std::unordered_set<TypeId> innerSeen;
|
||||||
|
|
||||||
|
// If we don't do this, and we have the misfortune of receiving a
|
||||||
|
// recursive union like:
|
||||||
|
//
|
||||||
|
// t1 where t1 = t1 | ExternType
|
||||||
|
//
|
||||||
|
// Then we are on a one way journey to a stack overflow.
|
||||||
|
if (FFlag::LuauAutocompleteUnionCopyPreviousSeen)
|
||||||
|
{
|
||||||
|
for (auto ty : seen)
|
||||||
|
{
|
||||||
|
if (is<UnionType, IntersectionType>(ty))
|
||||||
|
innerSeen.insert(ty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (isNil(*iter))
|
if (isNil(*iter))
|
||||||
{
|
{
|
||||||
++iter;
|
++iter;
|
||||||
|
@ -512,7 +587,7 @@ AutocompleteEntryMap autocompleteProps(
|
||||||
AutocompleteEntryMap autocompleteModuleTypes(const Module& module, const ScopePtr& scopeAtPosition, Position position, std::string_view moduleName)
|
AutocompleteEntryMap autocompleteModuleTypes(const Module& module, const ScopePtr& scopeAtPosition, Position position, std::string_view moduleName)
|
||||||
{
|
{
|
||||||
AutocompleteEntryMap result;
|
AutocompleteEntryMap result;
|
||||||
ScopePtr startScope = FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete ? scopeAtPosition : findScopeAtPosition(module, position);
|
ScopePtr startScope = scopeAtPosition;
|
||||||
for (ScopePtr& scope = startScope; scope; scope = scope->parent)
|
for (ScopePtr& scope = startScope; scope; scope = scope->parent)
|
||||||
{
|
{
|
||||||
if (auto it = scope->importedTypeBindings.find(std::string(moduleName)); it != scope->importedTypeBindings.end())
|
if (auto it = scope->importedTypeBindings.find(std::string(moduleName)); it != scope->importedTypeBindings.end())
|
||||||
|
@ -624,6 +699,30 @@ static std::optional<TypeId> findTypeElementAt(const AstTypeList& astTypeList, T
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static std::optional<TypeId> findTypeElementAt(AstTypePack* astTypePack, TypePackId tp, Position position)
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(FFlag::LuauStoreReturnTypesAsPackOnAst);
|
||||||
|
if (const auto typePack = astTypePack->as<AstTypePackExplicit>())
|
||||||
|
{
|
||||||
|
return findTypeElementAt(typePack->typeList, tp, position);
|
||||||
|
}
|
||||||
|
else if (const auto variadic = astTypePack->as<AstTypePackVariadic>())
|
||||||
|
{
|
||||||
|
if (variadic->location.containsClosed(position))
|
||||||
|
{
|
||||||
|
auto [_, tail] = flatten(tp);
|
||||||
|
|
||||||
|
if (tail)
|
||||||
|
{
|
||||||
|
if (const VariadicTypePack* vtp = get<VariadicTypePack>(follow(*tail)))
|
||||||
|
return findTypeElementAt(variadic->variadicType, vtp->ty, position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
static std::optional<TypeId> findTypeElementAt(AstType* astType, TypeId ty, Position position)
|
static std::optional<TypeId> findTypeElementAt(AstType* astType, TypeId ty, Position position)
|
||||||
{
|
{
|
||||||
ty = follow(ty);
|
ty = follow(ty);
|
||||||
|
@ -644,8 +743,16 @@ static std::optional<TypeId> findTypeElementAt(AstType* astType, TypeId ty, Posi
|
||||||
if (auto element = findTypeElementAt(type->argTypes, ftv->argTypes, position))
|
if (auto element = findTypeElementAt(type->argTypes, ftv->argTypes, position))
|
||||||
return element;
|
return element;
|
||||||
|
|
||||||
if (auto element = findTypeElementAt(type->returnTypes, ftv->retTypes, position))
|
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
|
||||||
return element;
|
{
|
||||||
|
if (auto element = findTypeElementAt(type->returnTypes, ftv->retTypes, position))
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (auto element = findTypeElementAt(type->returnTypes_DEPRECATED, ftv->retTypes, position))
|
||||||
|
return element;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// It's possible to walk through other types like intrsection and unions if we find value in doing that
|
// It's possible to walk through other types like intrsection and unions if we find value in doing that
|
||||||
|
@ -654,7 +761,7 @@ static std::optional<TypeId> findTypeElementAt(AstType* astType, TypeId ty, Posi
|
||||||
|
|
||||||
std::optional<TypeId> getLocalTypeInScopeAt(const Module& module, const ScopePtr& scopeAtPosition, Position position, AstLocal* local)
|
std::optional<TypeId> getLocalTypeInScopeAt(const Module& module, const ScopePtr& scopeAtPosition, Position position, AstLocal* local)
|
||||||
{
|
{
|
||||||
if (ScopePtr scope = FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete ? scopeAtPosition : findScopeAtPosition(module, position))
|
if (ScopePtr scope = scopeAtPosition)
|
||||||
{
|
{
|
||||||
for (const auto& [name, binding] : scope->bindings)
|
for (const auto& [name, binding] : scope->bindings)
|
||||||
{
|
{
|
||||||
|
@ -796,7 +903,7 @@ AutocompleteEntryMap autocompleteTypeNames(
|
||||||
{
|
{
|
||||||
AutocompleteEntryMap result;
|
AutocompleteEntryMap result;
|
||||||
|
|
||||||
ScopePtr startScope = FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete ? scopeAtPosition : findScopeAtPosition(module, position);
|
ScopePtr startScope = scopeAtPosition;
|
||||||
|
|
||||||
for (ScopePtr scope = startScope; scope; scope = scope->parent)
|
for (ScopePtr scope = startScope; scope; scope = scope->parent)
|
||||||
{
|
{
|
||||||
|
@ -975,29 +1082,46 @@ AutocompleteEntryMap autocompleteTypeNames(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!node->returnAnnotation)
|
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
|
||||||
return result;
|
|
||||||
|
|
||||||
for (size_t i = 0; i < node->returnAnnotation->types.size; i++)
|
|
||||||
{
|
{
|
||||||
AstType* ret = node->returnAnnotation->types.data[i];
|
if (!node->returnAnnotation)
|
||||||
|
return result;
|
||||||
|
|
||||||
if (ret->location.containsClosed(position))
|
if (const auto typePack = node->returnAnnotation->as<AstTypePackExplicit>())
|
||||||
{
|
{
|
||||||
if (const FunctionType* ftv = tryGetExpectedFunctionType(module, node))
|
for (size_t i = 0; i < typePack->typeList.types.size; i++)
|
||||||
{
|
{
|
||||||
if (auto ty = tryGetTypePackTypeAt(ftv->retTypes, i))
|
AstType* ret = typePack->typeList.types.data[i];
|
||||||
tryAddTypeCorrectSuggestion(result, startScope, topType, *ty, position);
|
|
||||||
|
if (ret->location.containsClosed(position))
|
||||||
|
{
|
||||||
|
if (const FunctionType* ftv = tryGetExpectedFunctionType(module, node))
|
||||||
|
{
|
||||||
|
if (auto ty = tryGetTypePackTypeAt(ftv->retTypes, i))
|
||||||
|
tryAddTypeCorrectSuggestion(result, startScope, topType, *ty, position);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: with additional type information, we could suggest inferred return type here
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: with additional type information, we could suggest inferred return type here
|
if (AstTypePack* retTp = typePack->typeList.tailType)
|
||||||
break;
|
{
|
||||||
|
if (auto variadic = retTp->as<AstTypePackVariadic>())
|
||||||
|
{
|
||||||
|
if (variadic->location.containsClosed(position))
|
||||||
|
{
|
||||||
|
if (const FunctionType* ftv = tryGetExpectedFunctionType(module, node))
|
||||||
|
{
|
||||||
|
if (auto ty = tryGetTypePackTypeAt(ftv->retTypes, ~0u))
|
||||||
|
tryAddTypeCorrectSuggestion(result, startScope, topType, *ty, position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
else if (auto variadic = node->returnAnnotation->as<AstTypePackVariadic>())
|
||||||
|
|
||||||
if (AstTypePack* retTp = node->returnAnnotation->tailType)
|
|
||||||
{
|
|
||||||
if (auto variadic = retTp->as<AstTypePackVariadic>())
|
|
||||||
{
|
{
|
||||||
if (variadic->location.containsClosed(position))
|
if (variadic->location.containsClosed(position))
|
||||||
{
|
{
|
||||||
|
@ -1009,6 +1133,43 @@ AutocompleteEntryMap autocompleteTypeNames(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!node->returnAnnotation_DEPRECATED)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < node->returnAnnotation_DEPRECATED->types.size; i++)
|
||||||
|
{
|
||||||
|
AstType* ret = node->returnAnnotation_DEPRECATED->types.data[i];
|
||||||
|
|
||||||
|
if (ret->location.containsClosed(position))
|
||||||
|
{
|
||||||
|
if (const FunctionType* ftv = tryGetExpectedFunctionType(module, node))
|
||||||
|
{
|
||||||
|
if (auto ty = tryGetTypePackTypeAt(ftv->retTypes, i))
|
||||||
|
tryAddTypeCorrectSuggestion(result, startScope, topType, *ty, position);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: with additional type information, we could suggest inferred return type here
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (AstTypePack* retTp = node->returnAnnotation_DEPRECATED->tailType)
|
||||||
|
{
|
||||||
|
if (auto variadic = retTp->as<AstTypePackVariadic>())
|
||||||
|
{
|
||||||
|
if (variadic->location.containsClosed(position))
|
||||||
|
{
|
||||||
|
if (const FunctionType* ftv = tryGetExpectedFunctionType(module, node))
|
||||||
|
{
|
||||||
|
if (auto ty = tryGetTypePackTypeAt(ftv->retTypes, ~0u))
|
||||||
|
tryAddTypeCorrectSuggestion(result, startScope, topType, *ty, position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
@ -1129,7 +1290,7 @@ static AutocompleteEntryMap autocompleteStatement(
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
// This is inefficient. :(
|
// This is inefficient. :(
|
||||||
ScopePtr scope = FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete ? scopeAtPosition : findScopeAtPosition(module, position);
|
ScopePtr scope = scopeAtPosition;
|
||||||
|
|
||||||
AutocompleteEntryMap result;
|
AutocompleteEntryMap result;
|
||||||
|
|
||||||
|
@ -1286,6 +1447,15 @@ static AutocompleteContext autocompleteExpression(
|
||||||
|
|
||||||
AstNode* node = ancestry.rbegin()[0];
|
AstNode* node = ancestry.rbegin()[0];
|
||||||
|
|
||||||
|
if (FFlag::DebugLuauMagicVariableNames)
|
||||||
|
{
|
||||||
|
InternalErrorReporter ice;
|
||||||
|
if (auto local = node->as<AstExprLocal>(); local && local->local->name == "_luau_autocomplete_ice")
|
||||||
|
ice.ice("_luau_autocomplete_ice encountered", local->location);
|
||||||
|
if (auto global = node->as<AstExprGlobal>(); global && global->name == "_luau_autocomplete_ice")
|
||||||
|
ice.ice("_luau_autocomplete_ice encountered", global->location);
|
||||||
|
}
|
||||||
|
|
||||||
if (node->is<AstExprIndexName>())
|
if (node->is<AstExprIndexName>())
|
||||||
{
|
{
|
||||||
if (auto it = module.astTypes.find(node->asExpr()))
|
if (auto it = module.astTypes.find(node->asExpr()))
|
||||||
|
@ -1298,7 +1468,7 @@ static AutocompleteContext autocompleteExpression(
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// This is inefficient. :(
|
// This is inefficient. :(
|
||||||
ScopePtr scope = FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete ? scopeAtPosition : findScopeAtPosition(module, position);
|
ScopePtr scope = scopeAtPosition;
|
||||||
|
|
||||||
while (scope)
|
while (scope)
|
||||||
{
|
{
|
||||||
|
@ -1367,7 +1537,7 @@ static AutocompleteResult autocompleteExpression(
|
||||||
return {result, ancestry, context};
|
return {result, ancestry, context};
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::optional<const ClassType*> getMethodContainingClass(const ModulePtr& module, AstExpr* funcExpr)
|
static std::optional<const ExternType*> getMethodContainingExternType(const ModulePtr& module, AstExpr* funcExpr)
|
||||||
{
|
{
|
||||||
AstExpr* parentExpr = nullptr;
|
AstExpr* parentExpr = nullptr;
|
||||||
if (auto indexName = funcExpr->as<AstExprIndexName>())
|
if (auto indexName = funcExpr->as<AstExprIndexName>())
|
||||||
|
@ -1391,14 +1561,14 @@ static std::optional<const ClassType*> getMethodContainingClass(const ModulePtr&
|
||||||
|
|
||||||
Luau::TypeId parentType = Luau::follow(*parentIt);
|
Luau::TypeId parentType = Luau::follow(*parentIt);
|
||||||
|
|
||||||
if (auto parentClass = Luau::get<ClassType>(parentType))
|
if (auto parentExternType = Luau::get<ExternType>(parentType))
|
||||||
{
|
{
|
||||||
return parentClass;
|
return parentExternType;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auto parentUnion = Luau::get<UnionType>(parentType))
|
if (auto parentUnion = Luau::get<UnionType>(parentType))
|
||||||
{
|
{
|
||||||
return returnFirstNonnullOptionOfType<ClassType>(parentUnion);
|
return returnFirstNonnullOptionOfType<ExternType>(parentUnion);
|
||||||
}
|
}
|
||||||
|
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
|
@ -1452,10 +1622,11 @@ static std::optional<AutocompleteEntryMap> convertRequireSuggestionsToAutocomple
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
|
|
||||||
AutocompleteEntryMap result;
|
AutocompleteEntryMap result;
|
||||||
for (const RequireSuggestion& suggestion : *suggestions)
|
for (RequireSuggestion& suggestion : *suggestions)
|
||||||
{
|
{
|
||||||
AutocompleteEntry entry = {AutocompleteEntryKind::RequirePath};
|
AutocompleteEntry entry = {AutocompleteEntryKind::RequirePath};
|
||||||
entry.insertText = std::move(suggestion.fullPath);
|
entry.insertText = std::move(suggestion.fullPath);
|
||||||
|
entry.tags = std::move(suggestion.tags);
|
||||||
result[std::move(suggestion.label)] = std::move(entry);
|
result[std::move(suggestion.label)] = std::move(entry);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
@ -1512,14 +1683,11 @@ static std::optional<AutocompleteEntryMap> autocompleteStringParams(
|
||||||
{
|
{
|
||||||
for (const std::string& tag : funcType->tags)
|
for (const std::string& tag : funcType->tags)
|
||||||
{
|
{
|
||||||
if (FFlag::AutocompleteRequirePathSuggestions2)
|
if (tag == kRequireTagName && fileResolver)
|
||||||
{
|
{
|
||||||
if (tag == kRequireTagName && fileResolver)
|
return convertRequireSuggestionsToAutocompleteEntryMap(fileResolver->getRequireSuggestions(module->name, candidateString));
|
||||||
{
|
|
||||||
return convertRequireSuggestionsToAutocompleteEntryMap(fileResolver->getRequireSuggestions(module->name, candidateString));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (std::optional<AutocompleteEntryMap> ret = callback(tag, getMethodContainingClass(module, candidate->func), candidateString))
|
if (std::optional<AutocompleteEntryMap> ret = callback(tag, getMethodContainingExternType(module, candidate->func), candidateString))
|
||||||
{
|
{
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
@ -1537,6 +1705,8 @@ static std::optional<AutocompleteEntryMap> autocompleteStringParams(
|
||||||
{
|
{
|
||||||
for (TypeId part : intersect->parts)
|
for (TypeId part : intersect->parts)
|
||||||
{
|
{
|
||||||
|
if (FFlag::LuauAutocompleteMissingFollows)
|
||||||
|
part = follow(part);
|
||||||
if (auto candidateFunctionType = Luau::get<FunctionType>(part))
|
if (auto candidateFunctionType = Luau::get<FunctionType>(part))
|
||||||
{
|
{
|
||||||
if (std::optional<AutocompleteEntryMap> ret = performCallback(candidateFunctionType))
|
if (std::optional<AutocompleteEntryMap> ret = performCallback(candidateFunctionType))
|
||||||
|
@ -1685,7 +1855,7 @@ static std::optional<AutocompleteEntry> makeAnonymousAutofilled(
|
||||||
if (!type)
|
if (!type)
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
|
|
||||||
const ScopePtr scope = FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete ? scopeAtPosition : findScopeAtPosition(*module, position);
|
const ScopePtr scope = scopeAtPosition;
|
||||||
if (!scope)
|
if (!scope)
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
|
|
||||||
|
@ -1702,13 +1872,14 @@ AutocompleteResult autocomplete_(
|
||||||
NotNull<BuiltinTypes> builtinTypes,
|
NotNull<BuiltinTypes> builtinTypes,
|
||||||
TypeArena* typeArena,
|
TypeArena* typeArena,
|
||||||
std::vector<AstNode*>& ancestry,
|
std::vector<AstNode*>& ancestry,
|
||||||
Scope* globalScope,
|
Scope* globalScope, // [TODO] This is unused argument, do we really need this?
|
||||||
const ScopePtr& scopeAtPosition,
|
const ScopePtr& scopeAtPosition,
|
||||||
Position position,
|
Position position,
|
||||||
FileResolver* fileResolver,
|
FileResolver* fileResolver,
|
||||||
StringCompletionCallback callback
|
StringCompletionCallback callback
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
|
LUAU_TIMETRACE_SCOPE("Luau::autocomplete_", "AutocompleteCore");
|
||||||
AstNode* node = ancestry.back();
|
AstNode* node = ancestry.back();
|
||||||
|
|
||||||
AstExprConstantNil dummy{Location{}};
|
AstExprConstantNil dummy{Location{}};
|
||||||
|
|
|
@ -3,22 +3,23 @@
|
||||||
|
|
||||||
#include "Luau/Ast.h"
|
#include "Luau/Ast.h"
|
||||||
#include "Luau/Clone.h"
|
#include "Luau/Clone.h"
|
||||||
|
#include "Luau/Common.h"
|
||||||
|
#include "Luau/ConstraintGenerator.h"
|
||||||
|
#include "Luau/ConstraintSolver.h"
|
||||||
#include "Luau/DenseHash.h"
|
#include "Luau/DenseHash.h"
|
||||||
#include "Luau/Error.h"
|
#include "Luau/Error.h"
|
||||||
#include "Luau/Frontend.h"
|
#include "Luau/Frontend.h"
|
||||||
#include "Luau/Symbol.h"
|
#include "Luau/InferPolarity.h"
|
||||||
#include "Luau/Common.h"
|
|
||||||
#include "Luau/ToString.h"
|
|
||||||
#include "Luau/ConstraintSolver.h"
|
|
||||||
#include "Luau/ConstraintGenerator.h"
|
|
||||||
#include "Luau/NotNull.h"
|
#include "Luau/NotNull.h"
|
||||||
#include "Luau/TypeInfer.h"
|
#include "Luau/Subtyping.h"
|
||||||
|
#include "Luau/Symbol.h"
|
||||||
|
#include "Luau/ToString.h"
|
||||||
|
#include "Luau/Type.h"
|
||||||
#include "Luau/TypeChecker2.h"
|
#include "Luau/TypeChecker2.h"
|
||||||
#include "Luau/TypeFunction.h"
|
#include "Luau/TypeFunction.h"
|
||||||
|
#include "Luau/TypeInfer.h"
|
||||||
#include "Luau/TypePack.h"
|
#include "Luau/TypePack.h"
|
||||||
#include "Luau/Type.h"
|
|
||||||
#include "Luau/TypeUtils.h"
|
#include "Luau/TypeUtils.h"
|
||||||
#include "Luau/Subtyping.h"
|
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
|
@ -29,51 +30,92 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauSolverV2)
|
LUAU_FASTFLAG(LuauSolverV2)
|
||||||
LUAU_DYNAMIC_FASTINT(LuauTypeSolverRelease)
|
LUAU_FASTFLAG(LuauNonReentrantGeneralization2)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauTypestateBuiltins2)
|
LUAU_FASTFLAGVARIABLE(LuauTableCloneClonesType3)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauStringFormatArityFix)
|
LUAU_FASTFLAGVARIABLE(LuauUserTypeFunTypecheck)
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauMagicFreezeCheckBlocked)
|
||||||
LUAU_FASTFLAG(AutocompleteRequirePathSuggestions2);
|
LUAU_FASTFLAGVARIABLE(LuauFormatUseLastPosition)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
|
||||||
static std::optional<WithPredicate<TypePackId>> magicFunctionSelect(
|
struct MagicSelect final : MagicFunction
|
||||||
TypeChecker& typechecker,
|
{
|
||||||
const ScopePtr& scope,
|
std::optional<WithPredicate<TypePackId>>
|
||||||
const AstExprCall& expr,
|
handleOldSolver(struct TypeChecker&, const std::shared_ptr<struct Scope>&, const class AstExprCall&, WithPredicate<TypePackId>) override;
|
||||||
WithPredicate<TypePackId> withPredicate
|
bool infer(const MagicFunctionCallContext& ctx) override;
|
||||||
);
|
};
|
||||||
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 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);
|
struct MagicAssert final : MagicFunction
|
||||||
static bool dcrMagicFunctionRequire(MagicFunctionCallContext context);
|
{
|
||||||
static bool dcrMagicFunctionPack(MagicFunctionCallContext context);
|
std::optional<WithPredicate<TypePackId>>
|
||||||
static bool dcrMagicFunctionFreeze(MagicFunctionCallContext context);
|
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)
|
TypeId makeUnion(TypeArena& arena, std::vector<TypeId>&& types)
|
||||||
{
|
{
|
||||||
|
@ -168,34 +210,10 @@ TypeId makeFunction(
|
||||||
return arena.addType(std::move(ftv));
|
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))
|
if (auto ftv = getMutable<FunctionType>(ty))
|
||||||
ftv->magicFunction = fn;
|
ftv->magic = std::move(magic);
|
||||||
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;
|
|
||||||
else
|
else
|
||||||
LUAU_ASSERT(!"Got a non functional type");
|
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)
|
void addGlobalBinding(GlobalTypes& globals, const ScopePtr& scope, const std::string& name, Binding binding)
|
||||||
{
|
{
|
||||||
|
inferGenericPolarities(NotNull{&globals.globalTypes}, NotNull{scope.get()}, binding.typeId);
|
||||||
scope->bindings[globals.globalNames.names->getOrAdd(name.c_str())] = binding;
|
scope->bindings[globals.globalNames.names->getOrAdd(name.c_str())] = binding;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeCheckForAutocomplete)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(!globals.globalTypes.types.isFrozen());
|
LUAU_ASSERT(!globals.globalTypes.types.isFrozen());
|
||||||
|
@ -278,6 +313,9 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
|
||||||
|
|
||||||
TypeArena& arena = globals.globalTypes;
|
TypeArena& arena = globals.globalTypes;
|
||||||
NotNull<BuiltinTypes> builtinTypes = globals.builtinTypes;
|
NotNull<BuiltinTypes> builtinTypes = globals.builtinTypes;
|
||||||
|
Scope* globalScope = nullptr; // NotNull<Scope> when removing FFlag::LuauNonReentrantGeneralization2
|
||||||
|
if (FFlag::LuauNonReentrantGeneralization2)
|
||||||
|
globalScope = globals.globalScope.get();
|
||||||
|
|
||||||
if (FFlag::LuauSolverV2)
|
if (FFlag::LuauSolverV2)
|
||||||
builtinTypeFunctions().addToScope(NotNull{&arena}, NotNull{globals.globalScope.get()});
|
builtinTypeFunctions().addToScope(NotNull{&arena}, NotNull{globals.globalScope.get()});
|
||||||
|
@ -287,8 +325,8 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
|
||||||
);
|
);
|
||||||
LUAU_ASSERT(loadResult.success);
|
LUAU_ASSERT(loadResult.success);
|
||||||
|
|
||||||
TypeId genericK = arena.addType(GenericType{"K"});
|
TypeId genericK = arena.addType(GenericType{globalScope, "K"});
|
||||||
TypeId genericV = arena.addType(GenericType{"V"});
|
TypeId genericV = arena.addType(GenericType{globalScope, "V"});
|
||||||
TypeId mapOfKtoV = arena.addType(TableType{{}, TableIndexer(genericK, genericV), globals.globalScope->level, TableState::Generic});
|
TypeId mapOfKtoV = arena.addType(TableType{{}, TableIndexer(genericK, genericV), globals.globalScope->level, TableState::Generic});
|
||||||
|
|
||||||
std::optional<TypeId> stringMetatableTy = getMetatable(builtinTypes->stringType, builtinTypes);
|
std::optional<TypeId> stringMetatableTy = getMetatable(builtinTypes->stringType, builtinTypes);
|
||||||
|
@ -301,6 +339,28 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
|
||||||
|
|
||||||
addGlobalBinding(globals, "string", it->second.type(), "@luau");
|
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)
|
// next<K, V>(t: Table<K, V>, i: K?) -> (K?, V)
|
||||||
TypePackId nextArgsTypePack = arena.addTypePack(TypePack{{mapOfKtoV, makeOption(builtinTypes, arena, genericK)}});
|
TypePackId nextArgsTypePack = arena.addTypePack(TypePack{{mapOfKtoV, makeOption(builtinTypes, arena, genericK)}});
|
||||||
TypePackId nextRetsTypePack = arena.addTypePack(TypePack{{makeOption(builtinTypes, arena, genericK), genericV}});
|
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)
|
// pairs<K, V>(t: Table<K, V>) -> ((Table<K, V>, K?) -> (K, V), Table<K, V>, nil)
|
||||||
addGlobalBinding(globals, "pairs", arena.addType(FunctionType{{genericK, genericV}, {}, pairsArgsTypePack, pairsReturnTypePack}), "@luau");
|
addGlobalBinding(globals, "pairs", arena.addType(FunctionType{{genericK, genericV}, {}, pairsArgsTypePack, pairsReturnTypePack}), "@luau");
|
||||||
|
|
||||||
TypeId genericMT = arena.addType(GenericType{"MT"});
|
TypeId genericMT = arena.addType(GenericType{globalScope, "MT"});
|
||||||
|
|
||||||
TableType tab{TableState::Generic, globals.globalScope->level};
|
TableType tab{TableState::Generic, globals.globalScope->level};
|
||||||
TypeId tabTy = arena.addType(tab);
|
TypeId tabTy = arena.addType(tab);
|
||||||
|
@ -326,7 +386,7 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
|
||||||
|
|
||||||
if (FFlag::LuauSolverV2)
|
if (FFlag::LuauSolverV2)
|
||||||
{
|
{
|
||||||
TypeId genericT = arena.addType(GenericType{"T"});
|
TypeId genericT = arena.addType(GenericType{globalScope, "T"});
|
||||||
TypeId tMetaMT = arena.addType(MetatableType{genericT, genericMT});
|
TypeId tMetaMT = arena.addType(MetatableType{genericT, genericMT});
|
||||||
|
|
||||||
// clang-format off
|
// clang-format off
|
||||||
|
@ -360,23 +420,30 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
|
||||||
// clang-format on
|
// clang-format on
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto& pair : globals.globalScope->bindings)
|
if (FFlag::LuauUserTypeFunTypecheck)
|
||||||
{
|
{
|
||||||
persist(pair.second.typeId);
|
finalizeGlobalBindings(globals.globalScope);
|
||||||
|
}
|
||||||
if (TableType* ttv = getMutable<TableType>(pair.second.typeId))
|
else
|
||||||
|
{
|
||||||
|
for (const auto& pair : globals.globalScope->bindings)
|
||||||
{
|
{
|
||||||
if (!ttv->name)
|
persist(pair.second.typeId);
|
||||||
ttv->name = "typeof(" + toString(pair.first) + ")";
|
|
||||||
|
if (TableType* ttv = getMutable<TableType>(pair.second.typeId))
|
||||||
|
{
|
||||||
|
if (!ttv->name)
|
||||||
|
ttv->name = "typeof(" + toString(pair.first) + ")";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
attachMagicFunction(getGlobalBinding(globals, "assert"), magicFunctionAssert);
|
attachMagicFunction(getGlobalBinding(globals, "assert"), std::make_shared<MagicAssert>());
|
||||||
|
|
||||||
if (FFlag::LuauSolverV2)
|
if (FFlag::LuauSolverV2)
|
||||||
{
|
{
|
||||||
// declare function assert<T>(value: T, errorMessage: string?): intersect<T, ~(false?)>
|
// declare function assert<T>(value: T, errorMessage: string?): intersect<T, ~(false?)>
|
||||||
TypeId genericT = arena.addType(GenericType{"T"});
|
TypeId genericT = arena.addType(GenericType{globalScope, "T"});
|
||||||
TypeId refinedTy = arena.addType(TypeFunctionInstanceType{
|
TypeId refinedTy = arena.addType(TypeFunctionInstanceType{
|
||||||
NotNull{&builtinTypeFunctions().intersectFunc}, {genericT, arena.addType(NegationType{builtinTypes->falsyType})}, {}
|
NotNull{&builtinTypeFunctions().intersectFunc}, {genericT, arena.addType(NegationType{builtinTypes->falsyType})}, {}
|
||||||
});
|
});
|
||||||
|
@ -387,9 +454,8 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
|
||||||
addGlobalBinding(globals, "assert", assertTy, "@luau");
|
addGlobalBinding(globals, "assert", assertTy, "@luau");
|
||||||
}
|
}
|
||||||
|
|
||||||
attachMagicFunction(getGlobalBinding(globals, "setmetatable"), magicFunctionSetMetaTable);
|
attachMagicFunction(getGlobalBinding(globals, "setmetatable"), std::make_shared<MagicSetMetatable>());
|
||||||
attachMagicFunction(getGlobalBinding(globals, "select"), magicFunctionSelect);
|
attachMagicFunction(getGlobalBinding(globals, "select"), std::make_shared<MagicSelect>());
|
||||||
attachDcrMagicFunction(getGlobalBinding(globals, "select"), dcrMagicFunctionSelect);
|
|
||||||
|
|
||||||
if (TableType* ttv = getMutable<TableType>(getGlobalBinding(globals, "table")))
|
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
|
// the top table type. We do the best we can by modelling these
|
||||||
// functions using unconstrained generics. It's not quite right,
|
// functions using unconstrained generics. It's not quite right,
|
||||||
// but it'll be ok for now.
|
// but it'll be ok for now.
|
||||||
TypeId genericTy = arena.addType(GenericType{"T"});
|
TypeId genericTy = arena.addType(GenericType{globalScope, "T"});
|
||||||
TypePackId thePack = arena.addTypePack({genericTy});
|
TypePackId thePack = arena.addTypePack({genericTy});
|
||||||
TypeId idTyWithMagic = arena.addType(FunctionType{{genericTy}, {}, thePack, thePack});
|
TypeId idTyWithMagic = arena.addType(FunctionType{{genericTy}, {}, thePack, thePack});
|
||||||
ttv->props["freeze"] = makeProperty(idTyWithMagic, "@luau/global/table.freeze");
|
ttv->props["freeze"] = makeProperty(idTyWithMagic, "@luau/global/table.freeze");
|
||||||
|
if (globalScope)
|
||||||
|
inferGenericPolarities(NotNull{&globals.globalTypes}, NotNull{globalScope}, idTyWithMagic);
|
||||||
|
|
||||||
TypeId idTy = arena.addType(FunctionType{{genericTy}, {}, thePack, thePack});
|
TypeId idTy = arena.addType(FunctionType{{genericTy}, {}, thePack, thePack});
|
||||||
|
if (globalScope)
|
||||||
|
inferGenericPolarities(NotNull{&globals.globalTypes}, NotNull{globalScope}, idTy);
|
||||||
ttv->props["clone"] = makeProperty(idTy, "@luau/global/table.clone");
|
ttv->props["clone"] = makeProperty(idTy, "@luau/global/table.clone");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -420,23 +490,67 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
|
||||||
ttv->props["foreach"].deprecated = true;
|
ttv->props["foreach"].deprecated = true;
|
||||||
ttv->props["foreachi"].deprecated = true;
|
ttv->props["foreachi"].deprecated = true;
|
||||||
|
|
||||||
attachMagicFunction(ttv->props["pack"].type(), magicFunctionPack);
|
attachMagicFunction(ttv->props["pack"].type(), std::make_shared<MagicPack>());
|
||||||
attachDcrMagicFunction(ttv->props["pack"].type(), dcrMagicFunctionPack);
|
if (FFlag::LuauTableCloneClonesType3)
|
||||||
if (FFlag::LuauTypestateBuiltins2)
|
attachMagicFunction(ttv->props["clone"].type(), std::make_shared<MagicClone>());
|
||||||
attachDcrMagicFunction(ttv->props["freeze"].type(), dcrMagicFunctionFreeze);
|
attachMagicFunction(ttv->props["freeze"].type(), std::make_shared<MagicFreeze>());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FFlag::AutocompleteRequirePathSuggestions2)
|
TypeId requireTy = getGlobalBinding(globals, "require");
|
||||||
|
attachTag(requireTy, kRequireTagName);
|
||||||
|
attachMagicFunction(requireTy, std::make_shared<MagicRequire>());
|
||||||
|
|
||||||
|
if (FFlag::LuauUserTypeFunTypecheck)
|
||||||
{
|
{
|
||||||
TypeId requireTy = getGlobalBinding(globals, "require");
|
// Global scope cannot be the parent of the type checking environment because it can be changed by the embedder
|
||||||
attachTag(requireTy, kRequireTagName);
|
globals.globalTypeFunctionScope->exportedTypeBindings = globals.globalScope->exportedTypeBindings;
|
||||||
attachMagicFunction(requireTy, magicFunctionRequire);
|
globals.globalTypeFunctionScope->builtinTypeNames = globals.globalScope->builtinTypeNames;
|
||||||
attachDcrMagicFunction(requireTy, dcrMagicFunctionRequire);
|
|
||||||
}
|
// Type function runtime also removes a few standard libraries and globals, so we will take only the ones that are defined
|
||||||
else
|
static const char* typeFunctionRuntimeBindings[] = {
|
||||||
{
|
// Libraries
|
||||||
attachMagicFunction(getGlobalBinding(globals, "require"), magicFunctionRequire);
|
"math",
|
||||||
attachDcrMagicFunction(getGlobalBinding(globals, "require"), dcrMagicFunctionRequire);
|
"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;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<WithPredicate<TypePackId>> magicFunctionFormat(
|
std::optional<WithPredicate<TypePackId>> MagicFormat::handleOldSolver(
|
||||||
TypeChecker& typechecker,
|
TypeChecker& typechecker,
|
||||||
const ScopePtr& scope,
|
const ScopePtr& scope,
|
||||||
const AstExprCall& expr,
|
const AstExprCall& expr,
|
||||||
|
@ -526,7 +640,7 @@ std::optional<WithPredicate<TypePackId>> magicFunctionFormat(
|
||||||
return WithPredicate<TypePackId>{arena.addTypePack({typechecker.stringType})};
|
return WithPredicate<TypePackId>{arena.addTypePack({typechecker.stringType})};
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool dcrMagicFunctionFormat(MagicFunctionCallContext context)
|
bool MagicFormat::infer(const MagicFunctionCallContext& context)
|
||||||
{
|
{
|
||||||
TypeArena* arena = context.solver->arena;
|
TypeArena* arena = context.solver->arena;
|
||||||
|
|
||||||
|
@ -570,7 +684,7 @@ static bool dcrMagicFunctionFormat(MagicFunctionCallContext context)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void dcrMagicFunctionTypeCheckFormat(MagicFunctionTypeCheckContext context)
|
bool MagicFormat::typeCheck(const MagicFunctionTypeCheckContext& context)
|
||||||
{
|
{
|
||||||
AstExprConstantString* fmt = nullptr;
|
AstExprConstantString* fmt = nullptr;
|
||||||
if (auto index = context.callSite->func->as<AstExprIndexName>(); index && context.callSite->self)
|
if (auto index = context.callSite->func->as<AstExprIndexName>(); index && context.callSite->self)
|
||||||
|
@ -586,11 +700,18 @@ static void dcrMagicFunctionTypeCheckFormat(MagicFunctionTypeCheckContext contex
|
||||||
|
|
||||||
if (!fmt)
|
if (!fmt)
|
||||||
{
|
{
|
||||||
if (FFlag::LuauStringFormatArityFix)
|
context.typechecker->reportError(CountMismatch{1, std::nullopt, 0, CountMismatch::Arg, true, "string.format"}, context.callSite->location);
|
||||||
context.typechecker->reportError(CountMismatch{1, std::nullopt, 0, CountMismatch::Arg, true, "string.format"}, context.callSite->location);
|
return true;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CLI-150726: The block below effectively constructs a type pack and then type checks it by going parameter-by-parameter.
|
||||||
|
// This does _not_ handle cases like:
|
||||||
|
//
|
||||||
|
// local foo : () -> (...string) = (nil :: any)
|
||||||
|
// print(string.format("%s %d %s", foo()))
|
||||||
|
//
|
||||||
|
// ... which should be disallowed.
|
||||||
|
|
||||||
std::vector<TypeId> expected = parseFormatString(context.builtinTypes, fmt->value.data, fmt->value.size);
|
std::vector<TypeId> expected = parseFormatString(context.builtinTypes, fmt->value.data, fmt->value.size);
|
||||||
const auto& [params, tail] = flatten(context.arguments);
|
const auto& [params, tail] = flatten(context.arguments);
|
||||||
|
|
||||||
|
@ -602,15 +723,30 @@ static void dcrMagicFunctionTypeCheckFormat(MagicFunctionTypeCheckContext contex
|
||||||
{
|
{
|
||||||
TypeId actualTy = params[i + paramOffset];
|
TypeId actualTy = params[i + paramOffset];
|
||||||
TypeId expectedTy = expected[i];
|
TypeId expectedTy = expected[i];
|
||||||
Location location = context.callSite->args.data[i + (calledWithSelf ? 0 : paramOffset)]->location;
|
Location location = FFlag::LuauFormatUseLastPosition
|
||||||
|
? context.callSite->args.data[std::min(context.callSite->args.size - 1, i + (calledWithSelf ? 0 : paramOffset))]->location
|
||||||
|
: context.callSite->args.data[i + (calledWithSelf ? 0 : paramOffset)]->location;
|
||||||
// use subtyping instead here
|
// use subtyping instead here
|
||||||
SubtypingResult result = context.typechecker->subtyping->isSubtype(actualTy, expectedTy, context.checkScope);
|
SubtypingResult result = context.typechecker->subtyping->isSubtype(actualTy, expectedTy, context.checkScope);
|
||||||
|
|
||||||
if (!result.isSubtype)
|
if (!result.isSubtype)
|
||||||
{
|
{
|
||||||
Reasonings reasonings = context.typechecker->explainReasonings(actualTy, expectedTy, location, result);
|
switch (shouldSuppressErrors(NotNull{&context.typechecker->normalizer}, actualTy))
|
||||||
context.typechecker->reportError(TypeMismatch{expectedTy, actualTy, reasonings.toString()}, location);
|
{
|
||||||
|
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)
|
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;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::optional<WithPredicate<TypePackId>> magicFunctionGmatch(
|
std::optional<WithPredicate<TypePackId>> MagicGmatch::handleOldSolver(
|
||||||
TypeChecker& typechecker,
|
TypeChecker& typechecker,
|
||||||
const ScopePtr& scope,
|
const ScopePtr& scope,
|
||||||
const AstExprCall& expr,
|
const AstExprCall& expr,
|
||||||
|
@ -709,7 +845,7 @@ static std::optional<WithPredicate<TypePackId>> magicFunctionGmatch(
|
||||||
return WithPredicate<TypePackId>{arena.addTypePack({iteratorType})};
|
return WithPredicate<TypePackId>{arena.addTypePack({iteratorType})};
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool dcrMagicFunctionGmatch(MagicFunctionCallContext context)
|
bool MagicGmatch::infer(const MagicFunctionCallContext& context)
|
||||||
{
|
{
|
||||||
const auto& [params, tail] = flatten(context.arguments);
|
const auto& [params, tail] = flatten(context.arguments);
|
||||||
|
|
||||||
|
@ -742,7 +878,7 @@ static bool dcrMagicFunctionGmatch(MagicFunctionCallContext context)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::optional<WithPredicate<TypePackId>> magicFunctionMatch(
|
std::optional<WithPredicate<TypePackId>> MagicMatch::handleOldSolver(
|
||||||
TypeChecker& typechecker,
|
TypeChecker& typechecker,
|
||||||
const ScopePtr& scope,
|
const ScopePtr& scope,
|
||||||
const AstExprCall& expr,
|
const AstExprCall& expr,
|
||||||
|
@ -782,7 +918,7 @@ static std::optional<WithPredicate<TypePackId>> magicFunctionMatch(
|
||||||
return WithPredicate<TypePackId>{returnList};
|
return WithPredicate<TypePackId>{returnList};
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool dcrMagicFunctionMatch(MagicFunctionCallContext context)
|
bool MagicMatch::infer(const MagicFunctionCallContext& context)
|
||||||
{
|
{
|
||||||
const auto& [params, tail] = flatten(context.arguments);
|
const auto& [params, tail] = flatten(context.arguments);
|
||||||
|
|
||||||
|
@ -818,7 +954,7 @@ static bool dcrMagicFunctionMatch(MagicFunctionCallContext context)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::optional<WithPredicate<TypePackId>> magicFunctionFind(
|
std::optional<WithPredicate<TypePackId>> MagicFind::handleOldSolver(
|
||||||
TypeChecker& typechecker,
|
TypeChecker& typechecker,
|
||||||
const ScopePtr& scope,
|
const ScopePtr& scope,
|
||||||
const AstExprCall& expr,
|
const AstExprCall& expr,
|
||||||
|
@ -876,7 +1012,7 @@ static std::optional<WithPredicate<TypePackId>> magicFunctionFind(
|
||||||
return WithPredicate<TypePackId>{returnList};
|
return WithPredicate<TypePackId>{returnList};
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool dcrMagicFunctionFind(MagicFunctionCallContext context)
|
bool MagicFind::infer(const MagicFunctionCallContext& context)
|
||||||
{
|
{
|
||||||
const auto& [params, tail] = flatten(context.arguments);
|
const auto& [params, tail] = flatten(context.arguments);
|
||||||
|
|
||||||
|
@ -953,11 +1089,9 @@ TypeId makeStringMetatable(NotNull<BuiltinTypes> builtinTypes)
|
||||||
|
|
||||||
|
|
||||||
FunctionType formatFTV{arena->addTypePack(TypePack{{stringType}, variadicTailPack}), oneStringPack};
|
FunctionType formatFTV{arena->addTypePack(TypePack{{stringType}, variadicTailPack}), oneStringPack};
|
||||||
formatFTV.magicFunction = &magicFunctionFormat;
|
|
||||||
formatFTV.isCheckedFunction = true;
|
formatFTV.isCheckedFunction = true;
|
||||||
const TypeId formatFn = arena->addType(formatFTV);
|
const TypeId formatFn = arena->addType(formatFTV);
|
||||||
attachDcrMagicFunction(formatFn, dcrMagicFunctionFormat);
|
attachMagicFunction(formatFn, std::make_shared<MagicFormat>());
|
||||||
attachDcrMagicFunctionTypeCheck(formatFn, dcrMagicFunctionTypeCheckFormat);
|
|
||||||
|
|
||||||
|
|
||||||
const TypeId stringToStringType = makeFunction(*arena, std::nullopt, {}, {}, {stringType}, {}, {stringType}, /* checked */ true);
|
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);
|
makeFunction(*arena, stringType, {}, {}, {stringType, replArgType, optionalNumber}, {}, {stringType, numberType}, /* checked */ false);
|
||||||
const TypeId gmatchFunc =
|
const TypeId gmatchFunc =
|
||||||
makeFunction(*arena, stringType, {}, {}, {stringType}, {}, {arena->addType(FunctionType{emptyPack, stringVariadicList})}, /* checked */ true);
|
makeFunction(*arena, stringType, {}, {}, {stringType}, {}, {arena->addType(FunctionType{emptyPack, stringVariadicList})}, /* checked */ true);
|
||||||
attachMagicFunction(gmatchFunc, magicFunctionGmatch);
|
attachMagicFunction(gmatchFunc, std::make_shared<MagicGmatch>());
|
||||||
attachDcrMagicFunction(gmatchFunc, dcrMagicFunctionGmatch);
|
|
||||||
|
|
||||||
FunctionType matchFuncTy{
|
FunctionType matchFuncTy{
|
||||||
arena->addTypePack({stringType, stringType, optionalNumber}), arena->addTypePack(TypePackVar{VariadicTypePack{stringType}})
|
arena->addTypePack({stringType, stringType, optionalNumber}), arena->addTypePack(TypePackVar{VariadicTypePack{stringType}})
|
||||||
};
|
};
|
||||||
matchFuncTy.isCheckedFunction = true;
|
matchFuncTy.isCheckedFunction = true;
|
||||||
const TypeId matchFunc = arena->addType(matchFuncTy);
|
const TypeId matchFunc = arena->addType(matchFuncTy);
|
||||||
attachMagicFunction(matchFunc, magicFunctionMatch);
|
attachMagicFunction(matchFunc, std::make_shared<MagicMatch>());
|
||||||
attachDcrMagicFunction(matchFunc, dcrMagicFunctionMatch);
|
|
||||||
|
|
||||||
FunctionType findFuncTy{
|
FunctionType findFuncTy{
|
||||||
arena->addTypePack({stringType, stringType, optionalNumber, optionalBoolean}),
|
arena->addTypePack({stringType, stringType, optionalNumber, optionalBoolean}),
|
||||||
|
@ -988,8 +1120,7 @@ TypeId makeStringMetatable(NotNull<BuiltinTypes> builtinTypes)
|
||||||
};
|
};
|
||||||
findFuncTy.isCheckedFunction = true;
|
findFuncTy.isCheckedFunction = true;
|
||||||
const TypeId findFunc = arena->addType(findFuncTy);
|
const TypeId findFunc = arena->addType(findFuncTy);
|
||||||
attachMagicFunction(findFunc, magicFunctionFind);
|
attachMagicFunction(findFunc, std::make_shared<MagicFind>());
|
||||||
attachDcrMagicFunction(findFunc, dcrMagicFunctionFind);
|
|
||||||
|
|
||||||
// string.byte : string -> number? -> number? -> ...number
|
// string.byte : string -> number? -> number? -> ...number
|
||||||
FunctionType stringDotByte{arena->addTypePack({stringType, optionalNumber, optionalNumber}), numberVariadicList};
|
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});
|
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,
|
TypeChecker& typechecker,
|
||||||
const ScopePtr& scope,
|
const ScopePtr& scope,
|
||||||
const AstExprCall& expr,
|
const AstExprCall& expr,
|
||||||
|
@ -1095,7 +1226,7 @@ static std::optional<WithPredicate<TypePackId>> magicFunctionSelect(
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool dcrMagicFunctionSelect(MagicFunctionCallContext context)
|
bool MagicSelect::infer(const MagicFunctionCallContext& context)
|
||||||
{
|
{
|
||||||
if (context.callSite->args.size <= 0)
|
if (context.callSite->args.size <= 0)
|
||||||
{
|
{
|
||||||
|
@ -1140,7 +1271,7 @@ static bool dcrMagicFunctionSelect(MagicFunctionCallContext context)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::optional<WithPredicate<TypePackId>> magicFunctionSetMetaTable(
|
std::optional<WithPredicate<TypePackId>> MagicSetMetatable::handleOldSolver(
|
||||||
TypeChecker& typechecker,
|
TypeChecker& typechecker,
|
||||||
const ScopePtr& scope,
|
const ScopePtr& scope,
|
||||||
const AstExprCall& expr,
|
const AstExprCall& expr,
|
||||||
|
@ -1222,7 +1353,12 @@ static std::optional<WithPredicate<TypePackId>> magicFunctionSetMetaTable(
|
||||||
return WithPredicate<TypePackId>{arena.addTypePack({target})};
|
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,
|
TypeChecker& typechecker,
|
||||||
const ScopePtr& scope,
|
const ScopePtr& scope,
|
||||||
const AstExprCall& expr,
|
const AstExprCall& expr,
|
||||||
|
@ -1256,7 +1392,12 @@ static std::optional<WithPredicate<TypePackId>> magicFunctionAssert(
|
||||||
return WithPredicate<TypePackId>{arena.addTypePack(TypePack{std::move(head), tail})};
|
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,
|
TypeChecker& typechecker,
|
||||||
const ScopePtr& scope,
|
const ScopePtr& scope,
|
||||||
const AstExprCall& expr,
|
const AstExprCall& expr,
|
||||||
|
@ -1299,7 +1440,7 @@ static std::optional<WithPredicate<TypePackId>> magicFunctionPack(
|
||||||
return WithPredicate<TypePackId>{arena.addTypePack({packedTable})};
|
return WithPredicate<TypePackId>{arena.addTypePack({packedTable})};
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool dcrMagicFunctionPack(MagicFunctionCallContext context)
|
bool MagicPack::infer(const MagicFunctionCallContext& context)
|
||||||
{
|
{
|
||||||
|
|
||||||
TypeArena* arena = context.solver->arena;
|
TypeArena* arena = context.solver->arena;
|
||||||
|
@ -1339,10 +1480,76 @@ static bool dcrMagicFunctionPack(MagicFunctionCallContext context)
|
||||||
return true;
|
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;
|
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))
|
if (auto mt = get<MetatableType>(inputType))
|
||||||
{
|
{
|
||||||
std::optional<TypeId> frozenTable = freezeTable(mt->table, context);
|
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.
|
// Clone the input type, this will become our final result type after we mutate it.
|
||||||
CloneState cloneState{context.solver->builtinTypes};
|
CloneState cloneState{context.solver->builtinTypes};
|
||||||
TypeId resultType = shallowClone(inputType, *arena, cloneState);
|
TypeId resultType = shallowClone(inputType, *arena, cloneState, /* ignorePersistent */ true);
|
||||||
auto tableTy = getMutable<TableType>(resultType);
|
auto tableTy = getMutable<TableType>(resultType);
|
||||||
// `clone` should not break this.
|
// `clone` should not break this.
|
||||||
LUAU_ASSERT(tableTy);
|
LUAU_ASSERT(tableTy);
|
||||||
|
@ -1384,10 +1591,14 @@ static std::optional<TypeId> freezeTable(TypeId inputType, MagicFunctionCallCont
|
||||||
return std::nullopt;
|
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;
|
TypeArena* arena = context.solver->arena;
|
||||||
const DataFlowGraph* dfg = context.solver->dfg.get();
|
const DataFlowGraph* dfg = context.solver->dfg.get();
|
||||||
Scope* scope = context.constraint->scope.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<DefId> resultDef = dfg->getDefOptional(targetExpr);
|
||||||
std::optional<TypeId> resultTy = resultDef ? scope->lookup(*resultDef) : std::nullopt;
|
std::optional<TypeId> resultTy = resultDef ? scope->lookup(*resultDef) : std::nullopt;
|
||||||
|
|
||||||
|
if (FFlag::LuauMagicFreezeCheckBlocked)
|
||||||
|
{
|
||||||
|
if (resultTy && !get<BlockedType>(resultTy))
|
||||||
|
{
|
||||||
|
// If there's an existing result type but it's _not_ blocked, then
|
||||||
|
// we aren't type stating this builtin and should fall back to
|
||||||
|
// regular inference.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
std::optional<TypeId> frozenType = freezeTable(inputType, context);
|
std::optional<TypeId> frozenType = freezeTable(inputType, context);
|
||||||
|
|
||||||
if (!frozenType)
|
if (!frozenType)
|
||||||
|
@ -1445,7 +1667,7 @@ static bool checkRequirePath(TypeChecker& typechecker, AstExpr* expr)
|
||||||
return good;
|
return good;
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::optional<WithPredicate<TypePackId>> magicFunctionRequire(
|
std::optional<WithPredicate<TypePackId>> MagicRequire::handleOldSolver(
|
||||||
TypeChecker& typechecker,
|
TypeChecker& typechecker,
|
||||||
const ScopePtr& scope,
|
const ScopePtr& scope,
|
||||||
const AstExprCall& expr,
|
const AstExprCall& expr,
|
||||||
|
@ -1491,7 +1713,7 @@ static bool checkRequirePathDcr(NotNull<ConstraintSolver> solver, AstExpr* expr)
|
||||||
return good;
|
return good;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool dcrMagicFunctionRequire(MagicFunctionCallContext context)
|
bool MagicRequire::infer(const MagicFunctionCallContext& context)
|
||||||
{
|
{
|
||||||
if (context.callSite->args.size != 1)
|
if (context.callSite->args.size != 1)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,16 +1,20 @@
|
||||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
#include "Luau/Clone.h"
|
#include "Luau/Clone.h"
|
||||||
|
|
||||||
|
#include "Luau/Common.h"
|
||||||
#include "Luau/NotNull.h"
|
#include "Luau/NotNull.h"
|
||||||
#include "Luau/Type.h"
|
#include "Luau/Type.h"
|
||||||
#include "Luau/TypePack.h"
|
#include "Luau/TypePack.h"
|
||||||
#include "Luau/Unifiable.h"
|
#include "Luau/Unifiable.h"
|
||||||
|
#include "Luau/VisitType.h"
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauSolverV2)
|
LUAU_FASTFLAG(LuauSolverV2)
|
||||||
|
|
||||||
// For each `Luau::clone` call, we will clone only up to N amount of types _and_ packs, as controlled by this limit.
|
// For each `Luau::clone` call, we will clone only up to N amount of types _and_ packs, as controlled by this limit.
|
||||||
LUAU_FASTINTVARIABLE(LuauTypeCloneIterationLimit, 100'000)
|
LUAU_FASTINTVARIABLE(LuauTypeCloneIterationLimit, 100'000)
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauClonedTableAndFunctionTypesMustHaveScopes)
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauDoNotClonePersistentBindings)
|
||||||
|
LUAU_FASTFLAG(LuauIncrementalAutocompleteDemandBasedCloning)
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
|
||||||
|
@ -27,6 +31,8 @@ const T* get(const Kind& kind)
|
||||||
|
|
||||||
class TypeCloner
|
class TypeCloner
|
||||||
{
|
{
|
||||||
|
|
||||||
|
protected:
|
||||||
NotNull<TypeArena> arena;
|
NotNull<TypeArena> arena;
|
||||||
NotNull<BuiltinTypes> builtinTypes;
|
NotNull<BuiltinTypes> builtinTypes;
|
||||||
|
|
||||||
|
@ -38,17 +44,31 @@ class TypeCloner
|
||||||
NotNull<SeenTypes> types;
|
NotNull<SeenTypes> types;
|
||||||
NotNull<SeenTypePacks> packs;
|
NotNull<SeenTypePacks> packs;
|
||||||
|
|
||||||
|
TypeId forceTy = nullptr;
|
||||||
|
TypePackId forceTp = nullptr;
|
||||||
|
|
||||||
int steps = 0;
|
int steps = 0;
|
||||||
|
|
||||||
public:
|
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)
|
: arena(arena)
|
||||||
, builtinTypes(builtinTypes)
|
, builtinTypes(builtinTypes)
|
||||||
, types(types)
|
, types(types)
|
||||||
, packs(packs)
|
, packs(packs)
|
||||||
|
, forceTy(forceTy)
|
||||||
|
, forceTp(forceTp)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
virtual ~TypeCloner() = default;
|
||||||
|
|
||||||
TypeId clone(TypeId ty)
|
TypeId clone(TypeId ty)
|
||||||
{
|
{
|
||||||
shallowClone(ty);
|
shallowClone(ty);
|
||||||
|
@ -107,12 +127,13 @@ private:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
std::optional<TypeId> find(TypeId ty) const
|
std::optional<TypeId> find(TypeId ty) const
|
||||||
{
|
{
|
||||||
ty = follow(ty, FollowOption::DisableLazyTypeThunks);
|
ty = follow(ty, FollowOption::DisableLazyTypeThunks);
|
||||||
if (auto it = types->find(ty); it != types->end())
|
if (auto it = types->find(ty); it != types->end())
|
||||||
return it->second;
|
return it->second;
|
||||||
else if (ty->persistent)
|
else if (ty->persistent && ty != forceTy)
|
||||||
return ty;
|
return ty;
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
@ -122,7 +143,7 @@ private:
|
||||||
tp = follow(tp);
|
tp = follow(tp);
|
||||||
if (auto it = packs->find(tp); it != packs->end())
|
if (auto it = packs->find(tp); it != packs->end())
|
||||||
return it->second;
|
return it->second;
|
||||||
else if (tp->persistent)
|
else if (tp->persistent && tp != forceTp)
|
||||||
return tp;
|
return tp;
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
@ -141,14 +162,14 @@ private:
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
TypeId shallowClone(TypeId ty)
|
virtual TypeId shallowClone(TypeId ty)
|
||||||
{
|
{
|
||||||
// We want to [`Luau::follow`] but without forcing the expansion of [`LazyType`]s.
|
// We want to [`Luau::follow`] but without forcing the expansion of [`LazyType`]s.
|
||||||
ty = follow(ty, FollowOption::DisableLazyTypeThunks);
|
ty = follow(ty, FollowOption::DisableLazyTypeThunks);
|
||||||
|
|
||||||
if (auto clone = find(ty))
|
if (auto clone = find(ty))
|
||||||
return *clone;
|
return *clone;
|
||||||
else if (ty->persistent)
|
else if (ty->persistent && ty != forceTy)
|
||||||
return ty;
|
return ty;
|
||||||
|
|
||||||
TypeId target = arena->addType(ty->ty);
|
TypeId target = arena->addType(ty->ty);
|
||||||
|
@ -158,8 +179,6 @@ public:
|
||||||
generic->scope = nullptr;
|
generic->scope = nullptr;
|
||||||
else if (auto free = getMutable<FreeType>(target))
|
else if (auto free = getMutable<FreeType>(target))
|
||||||
free->scope = nullptr;
|
free->scope = nullptr;
|
||||||
else if (auto fn = getMutable<FunctionType>(target))
|
|
||||||
fn->scope = nullptr;
|
|
||||||
else if (auto table = getMutable<TableType>(target))
|
else if (auto table = getMutable<TableType>(target))
|
||||||
table->scope = nullptr;
|
table->scope = nullptr;
|
||||||
|
|
||||||
|
@ -168,13 +187,13 @@ public:
|
||||||
return target;
|
return target;
|
||||||
}
|
}
|
||||||
|
|
||||||
TypePackId shallowClone(TypePackId tp)
|
virtual TypePackId shallowClone(TypePackId tp)
|
||||||
{
|
{
|
||||||
tp = follow(tp);
|
tp = follow(tp);
|
||||||
|
|
||||||
if (auto clone = find(tp))
|
if (auto clone = find(tp))
|
||||||
return *clone;
|
return *clone;
|
||||||
else if (tp->persistent)
|
else if (tp->persistent && tp != forceTp)
|
||||||
return tp;
|
return tp;
|
||||||
|
|
||||||
TypePackId target = arena->addTypePack(tp->ty);
|
TypePackId target = arena->addTypePack(tp->ty);
|
||||||
|
@ -257,8 +276,7 @@ private:
|
||||||
LUAU_ASSERT(!"Item holds neither TypeId nor TypePackId when enqueuing its children?");
|
LUAU_ASSERT(!"Item holds neither TypeId nor TypePackId when enqueuing its children?");
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrorType and ErrorTypePack is an alias to this type.
|
void cloneChildren(ErrorType* t)
|
||||||
void cloneChildren(Unifiable::Error* t)
|
|
||||||
{
|
{
|
||||||
// noop.
|
// noop.
|
||||||
}
|
}
|
||||||
|
@ -337,7 +355,7 @@ private:
|
||||||
t->metatable = shallowClone(t->metatable);
|
t->metatable = shallowClone(t->metatable);
|
||||||
}
|
}
|
||||||
|
|
||||||
void cloneChildren(ClassType* t)
|
void cloneChildren(ExternType* t)
|
||||||
{
|
{
|
||||||
for (auto& [_, p] : t->props)
|
for (auto& [_, p] : t->props)
|
||||||
p = shallowClone(p);
|
p = shallowClone(p);
|
||||||
|
@ -377,7 +395,7 @@ private:
|
||||||
ty = shallowClone(ty);
|
ty = shallowClone(ty);
|
||||||
}
|
}
|
||||||
|
|
||||||
void cloneChildren(LazyType* t)
|
virtual void cloneChildren(LazyType* t)
|
||||||
{
|
{
|
||||||
if (auto unwrapped = t->unwrapped.load())
|
if (auto unwrapped = t->unwrapped.load())
|
||||||
t->unwrapped.store(shallowClone(unwrapped));
|
t->unwrapped.store(shallowClone(unwrapped));
|
||||||
|
@ -428,6 +446,11 @@ private:
|
||||||
t->boundTo = shallowClone(t->boundTo);
|
t->boundTo = shallowClone(t->boundTo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void cloneChildren(ErrorTypePack* t)
|
||||||
|
{
|
||||||
|
// noop.
|
||||||
|
}
|
||||||
|
|
||||||
void cloneChildren(VariadicTypePack* t)
|
void cloneChildren(VariadicTypePack* t)
|
||||||
{
|
{
|
||||||
t->ty = shallowClone(t->ty);
|
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
|
} // 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;
|
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);
|
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;
|
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);
|
return cloner.shallowClone(typeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -477,7 +599,7 @@ TypePackId clone(TypePackId tp, TypeArena& dest, CloneState& cloneState)
|
||||||
if (tp->persistent)
|
if (tp->persistent)
|
||||||
return tp;
|
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);
|
return cloner.clone(tp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -486,13 +608,13 @@ TypeId clone(TypeId typeId, TypeArena& dest, CloneState& cloneState)
|
||||||
if (typeId->persistent)
|
if (typeId->persistent)
|
||||||
return typeId;
|
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);
|
return cloner.clone(typeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
TypeFun clone(const TypeFun& typeFun, TypeArena& dest, CloneState& cloneState)
|
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;
|
TypeFun copy = typeFun;
|
||||||
|
|
||||||
|
@ -517,4 +639,110 @@ TypeFun clone(const TypeFun& typeFun, TypeArena& dest, CloneState& cloneState)
|
||||||
return copy;
|
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
|
} // namespace Luau
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
#include "Luau/Constraint.h"
|
#include "Luau/Constraint.h"
|
||||||
#include "Luau/VisitType.h"
|
#include "Luau/VisitType.h"
|
||||||
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauDontRefCountTypesInTypeFunctions)
|
LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
@ -20,7 +20,7 @@ struct ReferenceCountInitializer : TypeOnceVisitor
|
||||||
|
|
||||||
DenseHashSet<TypeId>* result;
|
DenseHashSet<TypeId>* result;
|
||||||
|
|
||||||
ReferenceCountInitializer(DenseHashSet<TypeId>* result)
|
explicit ReferenceCountInitializer(DenseHashSet<TypeId>* result)
|
||||||
: result(result)
|
: result(result)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -43,26 +43,16 @@ struct ReferenceCountInitializer : TypeOnceVisitor
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool visit(TypeId ty, const ClassType&) override
|
bool visit(TypeId ty, const ExternType&) override
|
||||||
{
|
{
|
||||||
// ClassTypes never contain free types.
|
// ExternTypes never contain free types.
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool visit(TypeId, const TypeFunctionInstanceType&) override
|
bool visit(TypeId, const TypeFunctionInstanceType&) override
|
||||||
{
|
{
|
||||||
// We do not consider reference counted types that are inside a type
|
return FFlag::DebugLuauGreedyGeneralization;
|
||||||
// function to be part of the reachable reference counted types.
|
|
||||||
// Otherwise, code can be constructed in just the right way such
|
|
||||||
// that two type functions both claim to mutate a free type, which
|
|
||||||
// prevents either type function from trying to generalize it, so
|
|
||||||
// we potentially get stuck.
|
|
||||||
//
|
|
||||||
// The default behavior here is `true` for "visit the child types"
|
|
||||||
// of this type, hence:
|
|
||||||
return !FFlag::LuauDontRefCountTypesInTypeFunctions;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
bool isReferenceCountedType(const TypeId typ)
|
bool isReferenceCountedType(const TypeId typ)
|
||||||
|
@ -114,6 +104,11 @@ DenseHashSet<TypeId> Constraint::getMaybeMutatedFreeTypes() const
|
||||||
{
|
{
|
||||||
rci.traverse(fchc->argsPack);
|
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))
|
else if (auto ptc = get<PrimitiveTypeConstraint>(*this))
|
||||||
{
|
{
|
||||||
rci.traverse(ptc->freeType);
|
rci.traverse(ptc->freeType);
|
||||||
|
@ -121,12 +116,15 @@ DenseHashSet<TypeId> Constraint::getMaybeMutatedFreeTypes() const
|
||||||
else if (auto hpc = get<HasPropConstraint>(*this))
|
else if (auto hpc = get<HasPropConstraint>(*this))
|
||||||
{
|
{
|
||||||
rci.traverse(hpc->resultType);
|
rci.traverse(hpc->resultType);
|
||||||
// `HasPropConstraints` should not mutate `subjectType`.
|
if (FFlag::DebugLuauGreedyGeneralization)
|
||||||
|
rci.traverse(hpc->subjectType);
|
||||||
}
|
}
|
||||||
else if (auto hic = get<HasIndexerConstraint>(*this))
|
else if (auto hic = get<HasIndexerConstraint>(*this))
|
||||||
{
|
{
|
||||||
|
if (FFlag::DebugLuauGreedyGeneralization)
|
||||||
|
rci.traverse(hic->subjectType);
|
||||||
rci.traverse(hic->resultType);
|
rci.traverse(hic->resultType);
|
||||||
// `HasIndexerConstraint` should not mutate `subjectType` or `indexType`.
|
// `HasIndexerConstraint` should not mutate `indexType`.
|
||||||
}
|
}
|
||||||
else if (auto apc = get<AssignPropConstraint>(*this))
|
else if (auto apc = get<AssignPropConstraint>(*this))
|
||||||
{
|
{
|
||||||
|
@ -145,10 +143,18 @@ DenseHashSet<TypeId> Constraint::getMaybeMutatedFreeTypes() const
|
||||||
rci.traverse(ty);
|
rci.traverse(ty);
|
||||||
// `UnpackConstraint` should not mutate `sourcePack`.
|
// `UnpackConstraint` should not mutate `sourcePack`.
|
||||||
}
|
}
|
||||||
|
else if (auto rpc = get<ReduceConstraint>(*this); FFlag::DebugLuauGreedyGeneralization && rpc)
|
||||||
|
{
|
||||||
|
rci.traverse(rpc->ty);
|
||||||
|
}
|
||||||
else if (auto rpc = get<ReducePackConstraint>(*this))
|
else if (auto rpc = get<ReducePackConstraint>(*this))
|
||||||
{
|
{
|
||||||
rci.traverse(rpc->tp);
|
rci.traverse(rpc->tp);
|
||||||
}
|
}
|
||||||
|
else if (auto tcc = get<TableCheckConstraint>(*this))
|
||||||
|
{
|
||||||
|
rci.traverse(tcc->exprType);
|
||||||
|
}
|
||||||
|
|
||||||
return types;
|
return types;
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -13,33 +13,25 @@
|
||||||
|
|
||||||
LUAU_FASTFLAG(DebugLuauFreezeArena)
|
LUAU_FASTFLAG(DebugLuauFreezeArena)
|
||||||
LUAU_FASTFLAG(LuauSolverV2)
|
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
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
|
||||||
bool doesCallError(const AstExprCall* call); // TypeInfer.cpp
|
bool doesCallError(const AstExprCall* call); // TypeInfer.cpp
|
||||||
|
|
||||||
struct ReferencedDefFinder : public AstVisitor
|
|
||||||
{
|
|
||||||
bool visit(AstExprLocal* local) override
|
|
||||||
{
|
|
||||||
referencedLocalDefs.push_back(local->local);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// ast defs is just a mapping from expr -> def in general
|
|
||||||
// will get built up by the dfg builder
|
|
||||||
|
|
||||||
// localDefs, we need to copy over
|
|
||||||
std::vector<AstLocal*> referencedLocalDefs;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct PushScope
|
struct PushScope
|
||||||
{
|
{
|
||||||
ScopeStack& stack;
|
ScopeStack& stack;
|
||||||
|
size_t previousSize;
|
||||||
|
|
||||||
PushScope(ScopeStack& stack, DfgScope* scope)
|
PushScope(ScopeStack& stack, DfgScope* scope)
|
||||||
: stack(stack)
|
: stack(stack)
|
||||||
|
, previousSize(stack.size())
|
||||||
{
|
{
|
||||||
// `scope` should never be `nullptr` here.
|
// `scope` should never be `nullptr` here.
|
||||||
LUAU_ASSERT(scope);
|
LUAU_ASSERT(scope);
|
||||||
|
@ -48,7 +40,18 @@ struct PushScope
|
||||||
|
|
||||||
~PushScope()
|
~PushScope()
|
||||||
{
|
{
|
||||||
stack.pop_back();
|
if (FFlag::LuauDfgScopeStackTrueReset)
|
||||||
|
{
|
||||||
|
// If somehow this stack has _shrunk_ to be smaller than we expect,
|
||||||
|
// something very strange has happened.
|
||||||
|
LUAU_ASSERT(stack.size() > previousSize);
|
||||||
|
while (stack.size() > previousSize)
|
||||||
|
stack.pop_back();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
stack.pop_back();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -62,6 +65,12 @@ const RefinementKey* RefinementKeyArena::node(const RefinementKey* parent, DefId
|
||||||
return allocator.allocate(RefinementKey{parent, def, propName});
|
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
|
DefId DataFlowGraph::getDef(const AstExpr* expr) const
|
||||||
{
|
{
|
||||||
auto def = astDefs.find(expr);
|
auto def = astDefs.find(expr);
|
||||||
|
@ -77,12 +86,6 @@ std::optional<DefId> DataFlowGraph::getDefOptional(const AstExpr* expr) const
|
||||||
return NotNull{*def};
|
return NotNull{*def};
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<DefId> DataFlowGraph::getRValueDefForCompoundAssign(const AstExpr* expr) const
|
|
||||||
{
|
|
||||||
auto def = compoundAssignDefs.find(expr);
|
|
||||||
return def ? std::optional<DefId>(*def) : std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
DefId DataFlowGraph::getDef(const AstLocal* local) const
|
DefId DataFlowGraph::getDef(const AstLocal* local) const
|
||||||
{
|
{
|
||||||
auto def = localDefs.find(local);
|
auto def = localDefs.find(local);
|
||||||
|
@ -178,41 +181,33 @@ bool DfgScope::canUpdateDefinition(DefId def, const std::string& key) const
|
||||||
return true;
|
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,
|
AstStatBlock* block,
|
||||||
NotNull<InternalErrorReporter> handle
|
NotNull<DefArena> defArena,
|
||||||
|
NotNull<RefinementKeyArena> keyArena,
|
||||||
|
NotNull<struct InternalErrorReporter> handle
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
|
|
||||||
LUAU_TIMETRACE_SCOPE("DataFlowGraphBuilder::build", "Typechecking");
|
LUAU_TIMETRACE_SCOPE("DataFlowGraphBuilder::build", "Typechecking");
|
||||||
|
|
||||||
LUAU_ASSERT(FFlag::LuauSolverV2);
|
DataFlowGraphBuilder builder(defArena, keyArena);
|
||||||
|
|
||||||
DataFlowGraphBuilder builder;
|
|
||||||
builder.handle = handle;
|
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};
|
PushScope ps{builder.scopeStack, moduleScope};
|
||||||
builder.visitBlockWithoutChildScope(block);
|
builder.visitBlockWithoutChildScope(block);
|
||||||
builder.resolveCaptures();
|
builder.resolveCaptures();
|
||||||
|
@ -223,56 +218,6 @@ std::pair<std::shared_ptr<DataFlowGraph>, std::vector<std::unique_ptr<DfgScope>>
|
||||||
builder.keyArena->allocator.freeze();
|
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);
|
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())
|
if (scopeStack.empty())
|
||||||
return nullptr; // nullptr is the root DFG scope.
|
return nullptr; // nullptr is the root DFG scope.
|
||||||
return scopeStack.back();
|
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)
|
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.
|
// true if any of the considered scopes are a loop.
|
||||||
bool outsideLoopScope = false;
|
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;
|
scope->bindings[symbol] = result;
|
||||||
captures[symbol].allVersions.push_back(result);
|
captures[symbol].allVersions.push_back(result);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
DefId DataFlowGraphBuilder::lookup(DefId def, const std::string& key)
|
DefId DataFlowGraphBuilder::lookup(DefId def, const std::string& key, Location location)
|
||||||
{
|
{
|
||||||
DfgScope* scope = currentScope();
|
DfgScope* scope = FFlag::LuauDfgScopeStackNotNull ? currentScope() : currentScope_DEPRECATED();
|
||||||
for (DfgScope* current = scope; current; current = current->parent)
|
for (DfgScope* current = scope; current; current = current->parent)
|
||||||
{
|
{
|
||||||
if (auto props = current->props.find(def))
|
if (auto props = current->props.find(def))
|
||||||
|
@ -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
|
else if (auto phi = get<Phi>(def); phi && phi->operands.empty()) // Unresolved phi nodes
|
||||||
{
|
{
|
||||||
DefId result = defArena->freshCell();
|
DefId result = defArena->freshCell(def->name, location);
|
||||||
scope->props[def][key] = result;
|
scope->props[def][key] = result;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -433,7 +387,7 @@ DefId DataFlowGraphBuilder::lookup(DefId def, const std::string& key)
|
||||||
{
|
{
|
||||||
std::vector<DefId> defs;
|
std::vector<DefId> defs;
|
||||||
for (DefId operand : phi->operands)
|
for (DefId operand : phi->operands)
|
||||||
defs.push_back(lookup(operand, key));
|
defs.push_back(lookup(operand, key, location));
|
||||||
|
|
||||||
DefId result = defArena->phi(defs);
|
DefId result = defArena->phi(defs);
|
||||||
scope->props[def][key] = result;
|
scope->props[def][key] = result;
|
||||||
|
@ -441,7 +395,7 @@ DefId DataFlowGraphBuilder::lookup(DefId def, const std::string& key)
|
||||||
}
|
}
|
||||||
else if (get<Cell>(def))
|
else if (get<Cell>(def))
|
||||||
{
|
{
|
||||||
DefId result = defArena->freshCell();
|
DefId result = defArena->freshCell(def->name, location);
|
||||||
scope->props[def][key] = result;
|
scope->props[def][key] = result;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -451,7 +405,7 @@ DefId DataFlowGraphBuilder::lookup(DefId def, const std::string& key)
|
||||||
|
|
||||||
ControlFlow DataFlowGraphBuilder::visit(AstStatBlock* b)
|
ControlFlow DataFlowGraphBuilder::visit(AstStatBlock* b)
|
||||||
{
|
{
|
||||||
DfgScope* child = makeChildScope(b->location);
|
DfgScope* child = makeChildScope();
|
||||||
|
|
||||||
ControlFlow cf;
|
ControlFlow cf;
|
||||||
{
|
{
|
||||||
|
@ -459,7 +413,10 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatBlock* b)
|
||||||
cf = visitBlockWithoutChildScope(b);
|
cf = visitBlockWithoutChildScope(b);
|
||||||
}
|
}
|
||||||
|
|
||||||
currentScope()->inherit(child);
|
if (FFlag::LuauDfgScopeStackNotNull)
|
||||||
|
currentScope()->inherit(child);
|
||||||
|
else
|
||||||
|
currentScope_DEPRECATED()->inherit(child);
|
||||||
return cf;
|
return cf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -516,7 +473,7 @@ ControlFlow DataFlowGraphBuilder::visit(AstStat* s)
|
||||||
return visit(d);
|
return visit(d);
|
||||||
else if (auto d = s->as<AstStatDeclareFunction>())
|
else if (auto d = s->as<AstStatDeclareFunction>())
|
||||||
return visit(d);
|
return visit(d);
|
||||||
else if (auto d = s->as<AstStatDeclareClass>())
|
else if (auto d = s->as<AstStatDeclareExternType>())
|
||||||
return visit(d);
|
return visit(d);
|
||||||
else if (auto error = s->as<AstStatError>())
|
else if (auto error = s->as<AstStatError>())
|
||||||
return visit(error);
|
return visit(error);
|
||||||
|
@ -528,8 +485,8 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatIf* i)
|
||||||
{
|
{
|
||||||
visitExpr(i->condition);
|
visitExpr(i->condition);
|
||||||
|
|
||||||
DfgScope* thenScope = makeChildScope(i->thenbody->location);
|
DfgScope* thenScope = makeChildScope();
|
||||||
DfgScope* elseScope = makeChildScope(i->elsebody ? i->elsebody->location : i->location);
|
DfgScope* elseScope = makeChildScope();
|
||||||
|
|
||||||
ControlFlow thencf;
|
ControlFlow thencf;
|
||||||
{
|
{
|
||||||
|
@ -544,13 +501,27 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatIf* i)
|
||||||
elsecf = visit(i->elsebody);
|
elsecf = visit(i->elsebody);
|
||||||
}
|
}
|
||||||
|
|
||||||
DfgScope* scope = currentScope();
|
DfgScope* scope = FFlag::LuauDfgScopeStackNotNull ? currentScope() : currentScope_DEPRECATED();
|
||||||
if (thencf != ControlFlow::None && elsecf == ControlFlow::None)
|
if (FFlag::LuauDfgIfBlocksShouldRespectControlFlow)
|
||||||
join(scope, scope, elseScope);
|
{
|
||||||
else if (thencf == ControlFlow::None && elsecf != ControlFlow::None)
|
// If the control flow from the `if` or `else` block is non-linear,
|
||||||
join(scope, thenScope, scope);
|
// then we should assume that the _other_ branch is the one taken.
|
||||||
else if ((thencf | elsecf) == ControlFlow::None)
|
if (thencf != ControlFlow::None && elsecf == ControlFlow::None)
|
||||||
join(scope, thenScope, elseScope);
|
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)
|
if (thencf == elsecf)
|
||||||
return thencf;
|
return thencf;
|
||||||
|
@ -563,7 +534,7 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatIf* i)
|
||||||
ControlFlow DataFlowGraphBuilder::visit(AstStatWhile* w)
|
ControlFlow DataFlowGraphBuilder::visit(AstStatWhile* w)
|
||||||
{
|
{
|
||||||
// TODO(controlflow): entry point has a back edge from exit point
|
// 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};
|
PushScope ps{scopeStack, whileScope};
|
||||||
|
@ -571,7 +542,10 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatWhile* w)
|
||||||
visit(w->body);
|
visit(w->body);
|
||||||
}
|
}
|
||||||
|
|
||||||
currentScope()->inherit(whileScope);
|
if (FFlag::LuauDfgScopeStackNotNull)
|
||||||
|
currentScope()->inherit(whileScope);
|
||||||
|
else
|
||||||
|
currentScope_DEPRECATED()->inherit(whileScope);
|
||||||
|
|
||||||
return ControlFlow::None;
|
return ControlFlow::None;
|
||||||
}
|
}
|
||||||
|
@ -579,7 +553,7 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatWhile* w)
|
||||||
ControlFlow DataFlowGraphBuilder::visit(AstStatRepeat* r)
|
ControlFlow DataFlowGraphBuilder::visit(AstStatRepeat* r)
|
||||||
{
|
{
|
||||||
// TODO(controlflow): entry point has a back edge from exit point
|
// 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};
|
PushScope ps{scopeStack, repeatScope};
|
||||||
|
@ -587,7 +561,10 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatRepeat* r)
|
||||||
visitExpr(r->condition);
|
visitExpr(r->condition);
|
||||||
}
|
}
|
||||||
|
|
||||||
currentScope()->inherit(repeatScope);
|
if (FFlag::LuauDfgScopeStackNotNull)
|
||||||
|
currentScope()->inherit(repeatScope);
|
||||||
|
else
|
||||||
|
currentScope_DEPRECATED()->inherit(repeatScope);
|
||||||
|
|
||||||
return ControlFlow::None;
|
return ControlFlow::None;
|
||||||
}
|
}
|
||||||
|
@ -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
|
// We need to create a new def to intentionally avoid alias tracking, but we'd like to
|
||||||
// make sure that the non-aliased defs are also marked as a subscript for refinements.
|
// make sure that the non-aliased defs are also marked as a subscript for refinements.
|
||||||
bool subscripted = i < defs.size() && containsSubscriptedDefinition(defs[i]);
|
bool subscripted = i < defs.size() && containsSubscriptedDefinition(defs[i]);
|
||||||
DefId def = defArena->freshCell(subscripted);
|
DefId def = defArena->freshCell(local, local->location, subscripted);
|
||||||
if (i < l->values.size)
|
if (i < l->values.size)
|
||||||
{
|
{
|
||||||
AstExpr* e = l->values.data[i];
|
AstExpr* e = l->values.data[i];
|
||||||
|
@ -646,7 +623,10 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatLocal* l)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
graph.localDefs[local] = def;
|
graph.localDefs[local] = def;
|
||||||
currentScope()->bindings[local] = def;
|
if (FFlag::LuauDfgScopeStackNotNull)
|
||||||
|
currentScope()->bindings[local] = def;
|
||||||
|
else
|
||||||
|
currentScope_DEPRECATED()->bindings[local] = def;
|
||||||
captures[local].allVersions.push_back(def);
|
captures[local].allVersions.push_back(def);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -655,7 +635,7 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatLocal* l)
|
||||||
|
|
||||||
ControlFlow DataFlowGraphBuilder::visit(AstStatFor* f)
|
ControlFlow DataFlowGraphBuilder::visit(AstStatFor* f)
|
||||||
{
|
{
|
||||||
DfgScope* forScope = makeChildScope(f->location, DfgScope::Loop);
|
DfgScope* forScope = makeChildScope(DfgScope::Loop);
|
||||||
|
|
||||||
visitExpr(f->from);
|
visitExpr(f->from);
|
||||||
visitExpr(f->to);
|
visitExpr(f->to);
|
||||||
|
@ -668,23 +648,29 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatFor* f)
|
||||||
if (f->var->annotation)
|
if (f->var->annotation)
|
||||||
visitType(f->var->annotation);
|
visitType(f->var->annotation);
|
||||||
|
|
||||||
DefId def = defArena->freshCell();
|
DefId def = defArena->freshCell(f->var, f->var->location);
|
||||||
graph.localDefs[f->var] = def;
|
graph.localDefs[f->var] = def;
|
||||||
currentScope()->bindings[f->var] = def;
|
if (FFlag::LuauDfgScopeStackNotNull)
|
||||||
|
currentScope()->bindings[f->var] = def;
|
||||||
|
else
|
||||||
|
currentScope_DEPRECATED()->bindings[f->var] = def;
|
||||||
captures[f->var].allVersions.push_back(def);
|
captures[f->var].allVersions.push_back(def);
|
||||||
|
|
||||||
// TODO(controlflow): entry point has a back edge from exit point
|
// TODO(controlflow): entry point has a back edge from exit point
|
||||||
visit(f->body);
|
visit(f->body);
|
||||||
}
|
}
|
||||||
|
|
||||||
currentScope()->inherit(forScope);
|
if (FFlag::LuauDfgScopeStackNotNull)
|
||||||
|
currentScope()->inherit(forScope);
|
||||||
|
else
|
||||||
|
currentScope_DEPRECATED()->inherit(forScope);
|
||||||
|
|
||||||
return ControlFlow::None;
|
return ControlFlow::None;
|
||||||
}
|
}
|
||||||
|
|
||||||
ControlFlow DataFlowGraphBuilder::visit(AstStatForIn* f)
|
ControlFlow DataFlowGraphBuilder::visit(AstStatForIn* f)
|
||||||
{
|
{
|
||||||
DfgScope* forScope = makeChildScope(f->location, DfgScope::Loop);
|
DfgScope* forScope = makeChildScope(DfgScope::Loop);
|
||||||
|
|
||||||
{
|
{
|
||||||
PushScope ps{scopeStack, forScope};
|
PushScope ps{scopeStack, forScope};
|
||||||
|
@ -694,9 +680,12 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatForIn* f)
|
||||||
if (local->annotation)
|
if (local->annotation)
|
||||||
visitType(local->annotation);
|
visitType(local->annotation);
|
||||||
|
|
||||||
DefId def = defArena->freshCell();
|
DefId def = defArena->freshCell(local, local->location);
|
||||||
graph.localDefs[local] = def;
|
graph.localDefs[local] = def;
|
||||||
currentScope()->bindings[local] = def;
|
if (FFlag::LuauDfgScopeStackNotNull)
|
||||||
|
currentScope()->bindings[local] = def;
|
||||||
|
else
|
||||||
|
currentScope_DEPRECATED()->bindings[local] = def;
|
||||||
captures[local].allVersions.push_back(def);
|
captures[local].allVersions.push_back(def);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -707,8 +696,10 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatForIn* f)
|
||||||
|
|
||||||
visit(f->body);
|
visit(f->body);
|
||||||
}
|
}
|
||||||
|
if (FFlag::LuauDfgScopeStackNotNull)
|
||||||
currentScope()->inherit(forScope);
|
currentScope()->inherit(forScope);
|
||||||
|
else
|
||||||
|
currentScope_DEPRECATED()->inherit(forScope);
|
||||||
|
|
||||||
return ControlFlow::None;
|
return ControlFlow::None;
|
||||||
}
|
}
|
||||||
|
@ -723,7 +714,7 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatAssign* a)
|
||||||
for (size_t i = 0; i < a->vars.size; ++i)
|
for (size_t i = 0; i < a->vars.size; ++i)
|
||||||
{
|
{
|
||||||
AstExpr* v = a->vars.data[i];
|
AstExpr* v = a->vars.data[i];
|
||||||
visitLValue(v, i < defs.size() ? defs[i] : defArena->freshCell());
|
visitLValue(v, i < defs.size() ? defs[i] : defArena->freshCell(Symbol{}, v->location));
|
||||||
}
|
}
|
||||||
|
|
||||||
return ControlFlow::None;
|
return ControlFlow::None;
|
||||||
|
@ -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,
|
// which is evidence that references to variables must be a phi node of all possible definitions,
|
||||||
// but for bug compatibility, we'll assume the same thing here.
|
// but for bug compatibility, we'll assume the same thing here.
|
||||||
visitLValue(f->name, defArena->freshCell());
|
visitLValue(f->name, defArena->freshCell(Symbol{}, f->name->location));
|
||||||
visitExpr(f->func);
|
visitExpr(f->func);
|
||||||
|
|
||||||
if (auto local = f->name->as<AstExprLocal>())
|
if (auto local = f->name->as<AstExprLocal>())
|
||||||
|
@ -769,9 +760,12 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatFunction* f)
|
||||||
|
|
||||||
ControlFlow DataFlowGraphBuilder::visit(AstStatLocalFunction* l)
|
ControlFlow DataFlowGraphBuilder::visit(AstStatLocalFunction* l)
|
||||||
{
|
{
|
||||||
DefId def = defArena->freshCell();
|
DefId def = defArena->freshCell(l->name, l->location);
|
||||||
graph.localDefs[l->name] = def;
|
graph.localDefs[l->name] = def;
|
||||||
currentScope()->bindings[l->name] = def;
|
if (FFlag::LuauDfgScopeStackNotNull)
|
||||||
|
currentScope()->bindings[l->name] = def;
|
||||||
|
else
|
||||||
|
currentScope_DEPRECATED()->bindings[l->name] = def;
|
||||||
captures[l->name].allVersions.push_back(def);
|
captures[l->name].allVersions.push_back(def);
|
||||||
visitExpr(l->func);
|
visitExpr(l->func);
|
||||||
|
|
||||||
|
@ -780,7 +774,7 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatLocalFunction* l)
|
||||||
|
|
||||||
ControlFlow DataFlowGraphBuilder::visit(AstStatTypeAlias* t)
|
ControlFlow DataFlowGraphBuilder::visit(AstStatTypeAlias* t)
|
||||||
{
|
{
|
||||||
DfgScope* unreachable = makeChildScope(t->location);
|
DfgScope* unreachable = makeChildScope();
|
||||||
PushScope ps{scopeStack, unreachable};
|
PushScope ps{scopeStack, unreachable};
|
||||||
|
|
||||||
visitGenerics(t->generics);
|
visitGenerics(t->generics);
|
||||||
|
@ -792,7 +786,7 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatTypeAlias* t)
|
||||||
|
|
||||||
ControlFlow DataFlowGraphBuilder::visit(AstStatTypeFunction* f)
|
ControlFlow DataFlowGraphBuilder::visit(AstStatTypeFunction* f)
|
||||||
{
|
{
|
||||||
DfgScope* unreachable = makeChildScope(f->location);
|
DfgScope* unreachable = makeChildScope();
|
||||||
PushScope ps{scopeStack, unreachable};
|
PushScope ps{scopeStack, unreachable};
|
||||||
|
|
||||||
visitExpr(f->body);
|
visitExpr(f->body);
|
||||||
|
@ -802,9 +796,12 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatTypeFunction* f)
|
||||||
|
|
||||||
ControlFlow DataFlowGraphBuilder::visit(AstStatDeclareGlobal* d)
|
ControlFlow DataFlowGraphBuilder::visit(AstStatDeclareGlobal* d)
|
||||||
{
|
{
|
||||||
DefId def = defArena->freshCell();
|
DefId def = defArena->freshCell(d->name, d->nameLocation);
|
||||||
graph.declaredDefs[d] = def;
|
graph.declaredDefs[d] = def;
|
||||||
currentScope()->bindings[d->name] = def;
|
if (FFlag::LuauDfgScopeStackNotNull)
|
||||||
|
currentScope()->bindings[d->name] = def;
|
||||||
|
else
|
||||||
|
currentScope_DEPRECATED()->bindings[d->name] = def;
|
||||||
captures[d->name].allVersions.push_back(def);
|
captures[d->name].allVersions.push_back(def);
|
||||||
|
|
||||||
visitType(d->type);
|
visitType(d->type);
|
||||||
|
@ -814,31 +811,37 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatDeclareGlobal* d)
|
||||||
|
|
||||||
ControlFlow DataFlowGraphBuilder::visit(AstStatDeclareFunction* d)
|
ControlFlow DataFlowGraphBuilder::visit(AstStatDeclareFunction* d)
|
||||||
{
|
{
|
||||||
DefId def = defArena->freshCell();
|
DefId def = defArena->freshCell(d->name, d->nameLocation);
|
||||||
graph.declaredDefs[d] = def;
|
graph.declaredDefs[d] = def;
|
||||||
currentScope()->bindings[d->name] = def;
|
if (FFlag::LuauDfgScopeStackNotNull)
|
||||||
|
currentScope()->bindings[d->name] = def;
|
||||||
|
else
|
||||||
|
currentScope_DEPRECATED()->bindings[d->name] = def;
|
||||||
captures[d->name].allVersions.push_back(def);
|
captures[d->name].allVersions.push_back(def);
|
||||||
|
|
||||||
DfgScope* unreachable = makeChildScope(d->location);
|
DfgScope* unreachable = makeChildScope();
|
||||||
PushScope ps{scopeStack, unreachable};
|
PushScope ps{scopeStack, unreachable};
|
||||||
|
|
||||||
visitGenerics(d->generics);
|
visitGenerics(d->generics);
|
||||||
visitGenericPacks(d->genericPacks);
|
visitGenericPacks(d->genericPacks);
|
||||||
visitTypeList(d->params);
|
visitTypeList(d->params);
|
||||||
visitTypeList(d->retTypes);
|
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
|
||||||
|
visitTypePack(d->retTypes);
|
||||||
|
else
|
||||||
|
visitTypeList(d->retTypes_DEPRECATED);
|
||||||
|
|
||||||
return ControlFlow::None;
|
return ControlFlow::None;
|
||||||
}
|
}
|
||||||
|
|
||||||
ControlFlow DataFlowGraphBuilder::visit(AstStatDeclareClass* d)
|
ControlFlow DataFlowGraphBuilder::visit(AstStatDeclareExternType* d)
|
||||||
{
|
{
|
||||||
// This declaration does not "introduce" any bindings in value namespace,
|
// This declaration does not "introduce" any bindings in value namespace,
|
||||||
// so there's no symbolic value to begin with. We'll traverse the properties
|
// so there's no symbolic value to begin with. We'll traverse the properties
|
||||||
// because their type annotations may depend on something in the value namespace.
|
// because their type annotations may depend on something in the value namespace.
|
||||||
DfgScope* unreachable = makeChildScope(d->location);
|
DfgScope* unreachable = makeChildScope();
|
||||||
PushScope ps{scopeStack, unreachable};
|
PushScope ps{scopeStack, unreachable};
|
||||||
|
|
||||||
for (AstDeclaredClassProp prop : d->props)
|
for (AstDeclaredExternTypeProperty prop : d->props)
|
||||||
visitType(prop.ty);
|
visitType(prop.ty);
|
||||||
|
|
||||||
return ControlFlow::None;
|
return ControlFlow::None;
|
||||||
|
@ -846,7 +849,7 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatDeclareClass* d)
|
||||||
|
|
||||||
ControlFlow DataFlowGraphBuilder::visit(AstStatError* error)
|
ControlFlow DataFlowGraphBuilder::visit(AstStatError* error)
|
||||||
{
|
{
|
||||||
DfgScope* unreachable = makeChildScope(error->location);
|
DfgScope* unreachable = makeChildScope();
|
||||||
PushScope ps{scopeStack, unreachable};
|
PushScope ps{scopeStack, unreachable};
|
||||||
|
|
||||||
for (AstStat* s : error->statements)
|
for (AstStat* s : error->statements)
|
||||||
|
@ -871,19 +874,19 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExpr* e)
|
||||||
if (auto g = e->as<AstExprGroup>())
|
if (auto g = e->as<AstExprGroup>())
|
||||||
return visitExpr(g);
|
return visitExpr(g);
|
||||||
else if (auto c = e->as<AstExprConstantNil>())
|
else if (auto c = e->as<AstExprConstantNil>())
|
||||||
return {defArena->freshCell(), nullptr}; // ok
|
return {defArena->freshCell(Symbol{}, c->location), nullptr}; // ok
|
||||||
else if (auto c = e->as<AstExprConstantBool>())
|
else if (auto c = e->as<AstExprConstantBool>())
|
||||||
return {defArena->freshCell(), nullptr}; // ok
|
return {defArena->freshCell(Symbol{}, c->location), nullptr}; // ok
|
||||||
else if (auto c = e->as<AstExprConstantNumber>())
|
else if (auto c = e->as<AstExprConstantNumber>())
|
||||||
return {defArena->freshCell(), nullptr}; // ok
|
return {defArena->freshCell(Symbol{}, c->location), nullptr}; // ok
|
||||||
else if (auto c = e->as<AstExprConstantString>())
|
else if (auto c = e->as<AstExprConstantString>())
|
||||||
return {defArena->freshCell(), nullptr}; // ok
|
return {defArena->freshCell(Symbol{}, c->location), nullptr}; // ok
|
||||||
else if (auto l = e->as<AstExprLocal>())
|
else if (auto l = e->as<AstExprLocal>())
|
||||||
return visitExpr(l);
|
return visitExpr(l);
|
||||||
else if (auto g = e->as<AstExprGlobal>())
|
else if (auto g = e->as<AstExprGlobal>())
|
||||||
return visitExpr(g);
|
return visitExpr(g);
|
||||||
else if (auto v = e->as<AstExprVarargs>())
|
else if (auto v = e->as<AstExprVarargs>())
|
||||||
return {defArena->freshCell(), nullptr}; // ok
|
return {defArena->freshCell(Symbol{}, v->location), nullptr}; // ok
|
||||||
else if (auto c = e->as<AstExprCall>())
|
else if (auto c = e->as<AstExprCall>())
|
||||||
return visitExpr(c);
|
return visitExpr(c);
|
||||||
else if (auto i = e->as<AstExprIndexName>())
|
else if (auto i = e->as<AstExprIndexName>())
|
||||||
|
@ -924,14 +927,14 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprGroup* group)
|
||||||
|
|
||||||
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprLocal* l)
|
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprLocal* l)
|
||||||
{
|
{
|
||||||
DefId def = lookup(l->local);
|
DefId def = lookup(l->local, l->local->location);
|
||||||
const RefinementKey* key = keyArena->leaf(def);
|
const RefinementKey* key = keyArena->leaf(def);
|
||||||
return {def, key};
|
return {def, key};
|
||||||
}
|
}
|
||||||
|
|
||||||
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprGlobal* g)
|
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprGlobal* g)
|
||||||
{
|
{
|
||||||
DefId def = lookup(g->name);
|
DefId def = lookup(g->name, g->location);
|
||||||
return {def, keyArena->leaf(def)};
|
return {def, keyArena->leaf(def)};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -939,7 +942,13 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprCall* c)
|
||||||
{
|
{
|
||||||
visitExpr(c->func);
|
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();
|
AstExpr* firstArg = *c->args.begin();
|
||||||
|
|
||||||
|
@ -958,10 +967,7 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprCall* c)
|
||||||
|
|
||||||
LUAU_ASSERT(result);
|
LUAU_ASSERT(result);
|
||||||
|
|
||||||
Location location = currentScope()->location;
|
DfgScope* child = makeChildScope();
|
||||||
// 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);
|
|
||||||
scopeStack.push_back(child);
|
scopeStack.push_back(child);
|
||||||
|
|
||||||
auto [def, key] = *result;
|
auto [def, key] = *result;
|
||||||
|
@ -972,20 +978,31 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprCall* c)
|
||||||
visitLValue(firstArg, def);
|
visitLValue(firstArg, def);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (AstExpr* arg : c->args)
|
if (!FFlag::LuauPreprocessTypestatedArgument)
|
||||||
visitExpr(arg);
|
{
|
||||||
|
for (AstExpr* arg : c->args)
|
||||||
|
visitExpr(arg);
|
||||||
|
}
|
||||||
|
|
||||||
// calls should be treated as subscripted.
|
// We treat function calls as "subscripted" as they could potentially
|
||||||
return {defArena->freshCell(/* subscripted */ true), nullptr};
|
// return a subscripted value, consider:
|
||||||
|
//
|
||||||
|
// local function foo(tbl: {[string]: woof)
|
||||||
|
// return tbl["foobarbaz"]
|
||||||
|
// end
|
||||||
|
//
|
||||||
|
// local v = foo({})
|
||||||
|
//
|
||||||
|
// We want to consider `v` to be subscripted here.
|
||||||
|
return {defArena->freshCell(Symbol{}, c->location, /*subscripted=*/true)};
|
||||||
}
|
}
|
||||||
|
|
||||||
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprIndexName* i)
|
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprIndexName* i)
|
||||||
{
|
{
|
||||||
auto [parentDef, parentKey] = visitExpr(i->expr);
|
auto [parentDef, parentKey] = visitExpr(i->expr);
|
||||||
|
|
||||||
std::string index = i->index.value;
|
std::string index = i->index.value;
|
||||||
|
|
||||||
DefId def = lookup(parentDef, index);
|
DefId def = lookup(parentDef, index, i->location);
|
||||||
return {def, keyArena->node(parentKey, def, index)};
|
return {def, keyArena->node(parentKey, def, index)};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -998,16 +1015,16 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprIndexExpr* i)
|
||||||
{
|
{
|
||||||
std::string index{string->value.data, string->value.size};
|
std::string index{string->value.data, string->value.size};
|
||||||
|
|
||||||
DefId def = lookup(parentDef, index);
|
DefId def = lookup(parentDef, index, i->location);
|
||||||
return {def, keyArena->node(parentKey, def, index)};
|
return {def, keyArena->node(parentKey, def, index)};
|
||||||
}
|
}
|
||||||
|
|
||||||
return {defArena->freshCell(/* subscripted= */ true), nullptr};
|
return {defArena->freshCell(Symbol{}, i->location, /* subscripted= */ true), nullptr};
|
||||||
}
|
}
|
||||||
|
|
||||||
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprFunction* f)
|
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprFunction* f)
|
||||||
{
|
{
|
||||||
DfgScope* signatureScope = makeChildScope(f->location, DfgScope::Function);
|
DfgScope* signatureScope = makeChildScope(DfgScope::Function);
|
||||||
PushScope ps{scopeStack, signatureScope};
|
PushScope ps{scopeStack, signatureScope};
|
||||||
|
|
||||||
if (AstLocal* self = f->self)
|
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()`
|
// There's no syntax for `self` to have an annotation if using `function t:m()`
|
||||||
LUAU_ASSERT(!self->annotation);
|
LUAU_ASSERT(!self->annotation);
|
||||||
|
|
||||||
DefId def = defArena->freshCell();
|
DefId def = defArena->freshCell(f->debugname, f->location);
|
||||||
graph.localDefs[self] = def;
|
graph.localDefs[self] = def;
|
||||||
signatureScope->bindings[self] = def;
|
signatureScope->bindings[self] = def;
|
||||||
captures[self].allVersions.push_back(def);
|
captures[self].allVersions.push_back(def);
|
||||||
|
@ -1026,7 +1043,7 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprFunction* f)
|
||||||
if (param->annotation)
|
if (param->annotation)
|
||||||
visitType(param->annotation);
|
visitType(param->annotation);
|
||||||
|
|
||||||
DefId def = defArena->freshCell();
|
DefId def = defArena->freshCell(param, param->location);
|
||||||
graph.localDefs[param] = def;
|
graph.localDefs[param] = def;
|
||||||
signatureScope->bindings[param] = def;
|
signatureScope->bindings[param] = def;
|
||||||
captures[param].allVersions.push_back(def);
|
captures[param].allVersions.push_back(def);
|
||||||
|
@ -1035,8 +1052,16 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprFunction* f)
|
||||||
if (f->varargAnnotation)
|
if (f->varargAnnotation)
|
||||||
visitTypePack(f->varargAnnotation);
|
visitTypePack(f->varargAnnotation);
|
||||||
|
|
||||||
if (f->returnAnnotation)
|
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
|
||||||
visitTypeList(*f->returnAnnotation);
|
{
|
||||||
|
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
|
// TODO: function body can be re-entrant, as in mutations that occurs at the end of the function can also be
|
||||||
// visible to the beginning of the function, so statically speaking, the body of the function has an exit point
|
// visible to the beginning of the function, so statically speaking, the body of the function has an exit point
|
||||||
|
@ -1048,13 +1073,16 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprFunction* f)
|
||||||
// g() --> 5
|
// g() --> 5
|
||||||
visit(f->body);
|
visit(f->body);
|
||||||
|
|
||||||
return {defArena->freshCell(), nullptr};
|
return {defArena->freshCell(f->debugname, f->location), nullptr};
|
||||||
}
|
}
|
||||||
|
|
||||||
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprTable* t)
|
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprTable* t)
|
||||||
{
|
{
|
||||||
DefId tableCell = defArena->freshCell();
|
DefId tableCell = defArena->freshCell(Symbol{}, t->location);
|
||||||
currentScope()->props[tableCell] = {};
|
if (FFlag::LuauDfgScopeStackNotNull)
|
||||||
|
currentScope()->props[tableCell] = {};
|
||||||
|
else
|
||||||
|
currentScope_DEPRECATED()->props[tableCell] = {};
|
||||||
for (AstExprTable::Item item : t->items)
|
for (AstExprTable::Item item : t->items)
|
||||||
{
|
{
|
||||||
DataFlowResult result = visitExpr(item.value);
|
DataFlowResult result = visitExpr(item.value);
|
||||||
|
@ -1062,7 +1090,12 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprTable* t)
|
||||||
{
|
{
|
||||||
visitExpr(item.key);
|
visitExpr(item.key);
|
||||||
if (auto string = item.key->as<AstExprConstantString>())
|
if (auto string = item.key->as<AstExprConstantString>())
|
||||||
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);
|
visitExpr(u->expr);
|
||||||
|
|
||||||
return {defArena->freshCell(), nullptr};
|
return {defArena->freshCell(Symbol{}, u->location), nullptr};
|
||||||
}
|
}
|
||||||
|
|
||||||
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprBinary* b)
|
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprBinary* b)
|
||||||
|
@ -1081,7 +1114,7 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprBinary* b)
|
||||||
visitExpr(b->left);
|
visitExpr(b->left);
|
||||||
visitExpr(b->right);
|
visitExpr(b->right);
|
||||||
|
|
||||||
return {defArena->freshCell(), nullptr};
|
return {defArena->freshCell(Symbol{}, b->location), nullptr};
|
||||||
}
|
}
|
||||||
|
|
||||||
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprTypeAssertion* t)
|
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprTypeAssertion* t)
|
||||||
|
@ -1098,7 +1131,7 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprIfElse* i)
|
||||||
visitExpr(i->trueExpr);
|
visitExpr(i->trueExpr);
|
||||||
visitExpr(i->falseExpr);
|
visitExpr(i->falseExpr);
|
||||||
|
|
||||||
return {defArena->freshCell(), nullptr};
|
return {defArena->freshCell(Symbol{}, i->location), nullptr};
|
||||||
}
|
}
|
||||||
|
|
||||||
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprInterpString* i)
|
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprInterpString* i)
|
||||||
|
@ -1106,18 +1139,18 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprInterpString* i)
|
||||||
for (AstExpr* e : i->expressions)
|
for (AstExpr* e : i->expressions)
|
||||||
visitExpr(e);
|
visitExpr(e);
|
||||||
|
|
||||||
return {defArena->freshCell(), nullptr};
|
return {defArena->freshCell(Symbol{}, i->location), nullptr};
|
||||||
}
|
}
|
||||||
|
|
||||||
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprError* error)
|
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprError* error)
|
||||||
{
|
{
|
||||||
DfgScope* unreachable = makeChildScope(error->location);
|
DfgScope* unreachable = makeChildScope();
|
||||||
PushScope ps{scopeStack, unreachable};
|
PushScope ps{scopeStack, unreachable};
|
||||||
|
|
||||||
for (AstExpr* e : error->expressions)
|
for (AstExpr* e : error->expressions)
|
||||||
visitExpr(e);
|
visitExpr(e);
|
||||||
|
|
||||||
return {defArena->freshCell(), nullptr};
|
return {defArena->freshCell(Symbol{}, error->location), nullptr};
|
||||||
}
|
}
|
||||||
|
|
||||||
void DataFlowGraphBuilder::visitLValue(AstExpr* e, DefId incomingDef)
|
void DataFlowGraphBuilder::visitLValue(AstExpr* e, DefId incomingDef)
|
||||||
|
@ -1143,12 +1176,12 @@ void DataFlowGraphBuilder::visitLValue(AstExpr* e, DefId incomingDef)
|
||||||
|
|
||||||
DefId DataFlowGraphBuilder::visitLValue(AstExprLocal* l, DefId incomingDef)
|
DefId DataFlowGraphBuilder::visitLValue(AstExprLocal* l, DefId incomingDef)
|
||||||
{
|
{
|
||||||
DfgScope* scope = currentScope();
|
DfgScope* scope = FFlag::LuauDfgScopeStackNotNull ? currentScope() : currentScope_DEPRECATED();
|
||||||
|
|
||||||
// In order to avoid alias tracking, we need to clip the reference to the parent def.
|
// In order to avoid alias tracking, we need to clip the reference to the parent def.
|
||||||
if (scope->canUpdateDefinition(l->local))
|
if (scope->canUpdateDefinition(l->local) && !(FFlag::LuauDoNotAddUpvalueTypesToLocalType && l->upvalue))
|
||||||
{
|
{
|
||||||
DefId updated = defArena->freshCell(containsSubscriptedDefinition(incomingDef));
|
DefId updated = defArena->freshCell(l->local, l->location, containsSubscriptedDefinition(incomingDef));
|
||||||
scope->bindings[l->local] = updated;
|
scope->bindings[l->local] = updated;
|
||||||
captures[l->local].allVersions.push_back(updated);
|
captures[l->local].allVersions.push_back(updated);
|
||||||
return updated;
|
return updated;
|
||||||
|
@ -1159,12 +1192,12 @@ DefId DataFlowGraphBuilder::visitLValue(AstExprLocal* l, DefId incomingDef)
|
||||||
|
|
||||||
DefId DataFlowGraphBuilder::visitLValue(AstExprGlobal* g, DefId incomingDef)
|
DefId DataFlowGraphBuilder::visitLValue(AstExprGlobal* g, DefId incomingDef)
|
||||||
{
|
{
|
||||||
DfgScope* scope = currentScope();
|
DfgScope* scope = FFlag::LuauDfgScopeStackNotNull ? currentScope() : currentScope_DEPRECATED();
|
||||||
|
|
||||||
// In order to avoid alias tracking, we need to clip the reference to the parent def.
|
// In order to avoid alias tracking, we need to clip the reference to the parent def.
|
||||||
if (scope->canUpdateDefinition(g->name))
|
if (scope->canUpdateDefinition(g->name))
|
||||||
{
|
{
|
||||||
DefId updated = defArena->freshCell(containsSubscriptedDefinition(incomingDef));
|
DefId updated = defArena->freshCell(g->name, g->location, containsSubscriptedDefinition(incomingDef));
|
||||||
scope->bindings[g->name] = updated;
|
scope->bindings[g->name] = updated;
|
||||||
captures[g->name].allVersions.push_back(updated);
|
captures[g->name].allVersions.push_back(updated);
|
||||||
return updated;
|
return updated;
|
||||||
|
@ -1177,10 +1210,10 @@ DefId DataFlowGraphBuilder::visitLValue(AstExprIndexName* i, DefId incomingDef)
|
||||||
{
|
{
|
||||||
DefId parentDef = visitExpr(i->expr).def;
|
DefId parentDef = visitExpr(i->expr).def;
|
||||||
|
|
||||||
DfgScope* scope = currentScope();
|
DfgScope* scope = FFlag::LuauDfgScopeStackNotNull ? currentScope() : currentScope_DEPRECATED();
|
||||||
if (scope->canUpdateDefinition(parentDef, i->index.value))
|
if (scope->canUpdateDefinition(parentDef, i->index.value))
|
||||||
{
|
{
|
||||||
DefId updated = defArena->freshCell(containsSubscriptedDefinition(incomingDef));
|
DefId updated = defArena->freshCell(i->index, i->location, containsSubscriptedDefinition(incomingDef));
|
||||||
scope->props[parentDef][i->index.value] = updated;
|
scope->props[parentDef][i->index.value] = updated;
|
||||||
return updated;
|
return updated;
|
||||||
}
|
}
|
||||||
|
@ -1193,12 +1226,12 @@ DefId DataFlowGraphBuilder::visitLValue(AstExprIndexExpr* i, DefId incomingDef)
|
||||||
DefId parentDef = visitExpr(i->expr).def;
|
DefId parentDef = visitExpr(i->expr).def;
|
||||||
visitExpr(i->index);
|
visitExpr(i->index);
|
||||||
|
|
||||||
DfgScope* scope = currentScope();
|
DfgScope* scope = FFlag::LuauDfgScopeStackNotNull ? currentScope() : currentScope_DEPRECATED();
|
||||||
if (auto string = i->index->as<AstExprConstantString>())
|
if (auto string = i->index->as<AstExprConstantString>())
|
||||||
{
|
{
|
||||||
if (scope->canUpdateDefinition(parentDef, string->value.data))
|
if (scope->canUpdateDefinition(parentDef, string->value.data))
|
||||||
{
|
{
|
||||||
DefId updated = defArena->freshCell(containsSubscriptedDefinition(incomingDef));
|
DefId updated = defArena->freshCell(Symbol{}, i->location, containsSubscriptedDefinition(incomingDef));
|
||||||
scope->props[parentDef][string->value.data] = updated;
|
scope->props[parentDef][string->value.data] = updated;
|
||||||
return updated;
|
return updated;
|
||||||
}
|
}
|
||||||
|
@ -1206,7 +1239,7 @@ DefId DataFlowGraphBuilder::visitLValue(AstExprIndexExpr* i, DefId incomingDef)
|
||||||
return visitExpr(static_cast<AstExpr*>(i)).def;
|
return visitExpr(static_cast<AstExpr*>(i)).def;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
return defArena->freshCell(/*subscripted=*/true);
|
return defArena->freshCell(Symbol{}, i->location, /*subscripted=*/true);
|
||||||
}
|
}
|
||||||
|
|
||||||
DefId DataFlowGraphBuilder::visitLValue(AstExprError* error, DefId incomingDef)
|
DefId DataFlowGraphBuilder::visitLValue(AstExprError* error, DefId incomingDef)
|
||||||
|
@ -1224,6 +1257,8 @@ void DataFlowGraphBuilder::visitType(AstType* t)
|
||||||
return visitType(f);
|
return visitType(f);
|
||||||
else if (auto tyof = t->as<AstTypeTypeof>())
|
else if (auto tyof = t->as<AstTypeTypeof>())
|
||||||
return visitType(tyof);
|
return visitType(tyof);
|
||||||
|
else if (auto o = t->as<AstTypeOptional>())
|
||||||
|
return;
|
||||||
else if (auto u = t->as<AstTypeUnion>())
|
else if (auto u = t->as<AstTypeUnion>())
|
||||||
return visitType(u);
|
return visitType(u);
|
||||||
else if (auto i = t->as<AstTypeIntersection>())
|
else if (auto i = t->as<AstTypeIntersection>())
|
||||||
|
@ -1234,6 +1269,8 @@ void DataFlowGraphBuilder::visitType(AstType* t)
|
||||||
return; // ok
|
return; // ok
|
||||||
else if (auto s = t->as<AstTypeSingletonString>())
|
else if (auto s = t->as<AstTypeSingletonString>())
|
||||||
return; // ok
|
return; // ok
|
||||||
|
else if (auto g = t->as<AstTypeGroup>())
|
||||||
|
return visitType(g->type);
|
||||||
else
|
else
|
||||||
handle->ice("Unknown AstType in DataFlowGraphBuilder::visitType");
|
handle->ice("Unknown AstType in DataFlowGraphBuilder::visitType");
|
||||||
}
|
}
|
||||||
|
@ -1266,7 +1303,10 @@ void DataFlowGraphBuilder::visitType(AstTypeFunction* f)
|
||||||
visitGenerics(f->generics);
|
visitGenerics(f->generics);
|
||||||
visitGenericPacks(f->genericPacks);
|
visitGenericPacks(f->genericPacks);
|
||||||
visitTypeList(f->argTypes);
|
visitTypeList(f->argTypes);
|
||||||
visitTypeList(f->returnTypes);
|
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
|
||||||
|
visitTypePack(f->returnTypes);
|
||||||
|
else
|
||||||
|
visitTypeList(f->returnTypes_DEPRECATED);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DataFlowGraphBuilder::visitType(AstTypeTypeof* t)
|
void DataFlowGraphBuilder::visitType(AstTypeTypeof* t)
|
||||||
|
@ -1323,21 +1363,21 @@ void DataFlowGraphBuilder::visitTypeList(AstTypeList l)
|
||||||
visitTypePack(l.tailType);
|
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)
|
if (generic->defaultValue)
|
||||||
visitType(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)
|
if (generic->defaultValue)
|
||||||
visitTypePack(generic.defaultValue);
|
visitTypePack(generic->defaultValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -36,9 +36,9 @@ void collectOperands(DefId def, std::vector<DefId>* operands)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DefId DefArena::freshCell(bool subscripted)
|
DefId DefArena::freshCell(Symbol sym, Location location, bool subscripted)
|
||||||
{
|
{
|
||||||
return NotNull{allocator.allocate(Def{Cell{subscripted}})};
|
return NotNull{allocator.allocate(Def{Cell{subscripted}, sym, location})};
|
||||||
}
|
}
|
||||||
|
|
||||||
DefId DefArena::phi(DefId a, DefId b)
|
DefId DefArena::phi(DefId a, DefId b)
|
||||||
|
|
|
@ -13,7 +13,6 @@
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
|
||||||
std::string DiffPathNode::toString() const
|
std::string DiffPathNode::toString() const
|
||||||
{
|
{
|
||||||
switch (kind)
|
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 diffFunction(DifferEnvironment& env, TypeId left, TypeId right);
|
||||||
static DifferResult diffGeneric(DifferEnvironment& env, TypeId left, TypeId right);
|
static DifferResult diffGeneric(DifferEnvironment& env, TypeId left, TypeId right);
|
||||||
static DifferResult diffNegation(DifferEnvironment& env, TypeId left, TypeId right);
|
static DifferResult diffNegation(DifferEnvironment& env, TypeId left, TypeId right);
|
||||||
static DifferResult diffClass(DifferEnvironment& env, TypeId left, TypeId right);
|
static DifferResult diffExternType(DifferEnvironment& env, TypeId left, TypeId right);
|
||||||
struct FindSeteqCounterexampleResult
|
struct FindSeteqCounterexampleResult
|
||||||
{
|
{
|
||||||
// nullopt if no counterexample found
|
// nullopt if no counterexample found
|
||||||
|
@ -482,14 +481,14 @@ static DifferResult diffNegation(DifferEnvironment& env, TypeId left, TypeId rig
|
||||||
return differResult;
|
return differResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
static DifferResult diffClass(DifferEnvironment& env, TypeId left, TypeId right)
|
static DifferResult diffExternType(DifferEnvironment& env, TypeId left, TypeId right)
|
||||||
{
|
{
|
||||||
const ClassType* leftClass = get<ClassType>(left);
|
const ExternType* leftExternType = get<ExternType>(left);
|
||||||
const ClassType* rightClass = get<ClassType>(right);
|
const ExternType* rightExternType = get<ExternType>(right);
|
||||||
LUAU_ASSERT(leftClass);
|
LUAU_ASSERT(leftExternType);
|
||||||
LUAU_ASSERT(rightClass);
|
LUAU_ASSERT(rightExternType);
|
||||||
|
|
||||||
if (leftClass == rightClass)
|
if (leftExternType == rightExternType)
|
||||||
{
|
{
|
||||||
return DifferResult{};
|
return DifferResult{};
|
||||||
}
|
}
|
||||||
|
@ -652,9 +651,9 @@ static DifferResult diffUsingEnv(DifferEnvironment& env, TypeId left, TypeId rig
|
||||||
{
|
{
|
||||||
return diffNegation(env, left, right);
|
return diffNegation(env, left, right);
|
||||||
}
|
}
|
||||||
else if (auto lc = get<ClassType>(left))
|
else if (auto lc = get<ExternType>(left))
|
||||||
{
|
{
|
||||||
return diffClass(env, left, right);
|
return diffExternType(env, left, right);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw InternalCompilerError{"Unimplemented Simple TypeId variant for diffing"};
|
throw InternalCompilerError{"Unimplemented Simple TypeId variant for diffing"};
|
||||||
|
@ -719,7 +718,7 @@ static DifferResult diffUsingEnv(DifferEnvironment& env, TypeId left, TypeId rig
|
||||||
env.popVisiting();
|
env.popVisiting();
|
||||||
return diffRes;
|
return diffRes;
|
||||||
}
|
}
|
||||||
if (auto le = get<Luau::Unifiable::Error>(left))
|
if (auto le = get<ErrorType>(left))
|
||||||
{
|
{
|
||||||
// TODO: return debug-friendly result state
|
// TODO: return debug-friendly result state
|
||||||
env.popVisiting();
|
env.popVisiting();
|
||||||
|
@ -945,14 +944,12 @@ std::vector<std::pair<TypeId, TypeId>>::const_reverse_iterator DifferEnvironment
|
||||||
return visitingStack.crend();
|
return visitingStack.crend();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
DifferResult diff(TypeId ty1, TypeId ty2)
|
DifferResult diff(TypeId ty1, TypeId ty2)
|
||||||
{
|
{
|
||||||
DifferEnvironment differEnv{ty1, ty2, std::nullopt, std::nullopt};
|
DifferEnvironment differEnv{ty1, ty2, std::nullopt, std::nullopt};
|
||||||
return diffUsingEnv(differEnv, ty1, ty2);
|
return diffUsingEnv(differEnv, ty1, ty2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
DifferResult diffWithSymbols(TypeId ty1, TypeId ty2, std::optional<std::string> symbol1, std::optional<std::string> symbol2)
|
DifferResult diffWithSymbols(TypeId ty1, TypeId ty2, std::optional<std::string> symbol1, std::optional<std::string> symbol2)
|
||||||
{
|
{
|
||||||
DifferEnvironment differEnv{ty1, ty2, symbol1, symbol2};
|
DifferEnvironment differEnv{ty1, ty2, symbol1, symbol2};
|
||||||
|
@ -963,7 +960,7 @@ bool isSimple(TypeId ty)
|
||||||
{
|
{
|
||||||
ty = follow(ty);
|
ty = follow(ty);
|
||||||
// TODO: think about GenericType, etc.
|
// TODO: think about GenericType, etc.
|
||||||
return get<PrimitiveType>(ty) || get<SingletonType>(ty) || get<AnyType>(ty) || get<NegationType>(ty) || get<ClassType>(ty) ||
|
return get<PrimitiveType>(ty) || get<SingletonType>(ty) || get<AnyType>(ty) || get<NegationType>(ty) || get<ExternType>(ty) ||
|
||||||
get<UnknownType>(ty) || get<NeverType>(ty);
|
get<UnknownType>(ty) || get<NeverType>(ty);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,107 +1,13 @@
|
||||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
#include "Luau/BuiltinDefinitions.h"
|
#include "Luau/BuiltinDefinitions.h"
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauMathMap)
|
LUAU_FASTFLAG(LuauDeclareExternType)
|
||||||
|
LUAU_FASTFLAG(LuauTypeFunOptional)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauVectorDefinitions)
|
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
|
||||||
// TODO: there has to be a better way, like splitting up per library
|
static const std::string kBuiltinDefinitionBaseSrc = R"BUILTIN_SRC(
|
||||||
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,
|
|
||||||
}
|
|
||||||
|
|
||||||
@checked declare function require(target: any): any
|
@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
|
@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.
|
-- 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
|
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";
|
)BUILTIN_SRC";
|
||||||
|
|
||||||
static const std::string kBuiltinDefinitionLuaSrcChecked = R"BUILTIN_SRC(
|
static const std::string kBuiltinDefinitionBit32Src = R"BUILTIN_SRC(
|
||||||
|
|
||||||
declare bit32: {
|
declare bit32: {
|
||||||
band: @checked (...number) -> number,
|
band: @checked (...number) -> number,
|
||||||
|
@ -250,6 +80,10 @@ declare bit32: {
|
||||||
byteswap: @checked (n: number) -> number,
|
byteswap: @checked (n: number) -> number,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
)BUILTIN_SRC";
|
||||||
|
|
||||||
|
static const std::string kBuiltinDefinitionMathSrc = R"BUILTIN_SRC(
|
||||||
|
|
||||||
declare math: {
|
declare math: {
|
||||||
frexp: @checked (n: number) -> (number, number),
|
frexp: @checked (n: number) -> (number, number),
|
||||||
ldexp: @checked (s: number, e: number) -> number,
|
ldexp: @checked (s: number, e: number) -> number,
|
||||||
|
@ -294,8 +128,13 @@ declare math: {
|
||||||
noise: @checked (x: number, y: number?, z: number?) -> number,
|
noise: @checked (x: number, y: number?, z: number?) -> number,
|
||||||
round: @checked (n: number) -> number,
|
round: @checked (n: number) -> number,
|
||||||
map: @checked (x: number, inmin: number, inmax: number, outmin: number, outmax: 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 = {
|
type DateTypeArg = {
|
||||||
year: number,
|
year: number,
|
||||||
month: number,
|
month: number,
|
||||||
|
@ -325,51 +164,9 @@ declare os: {
|
||||||
clock: () -> number,
|
clock: () -> number,
|
||||||
}
|
}
|
||||||
|
|
||||||
@checked declare function require(target: any): any
|
)BUILTIN_SRC";
|
||||||
|
|
||||||
@checked declare function getfenv(target: any): { [string]: any }
|
static const std::string kBuiltinDefinitionCoroutineSrc = R"BUILTIN_SRC(
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
declare coroutine: {
|
declare coroutine: {
|
||||||
create: <A..., R...>(f: (A...) -> R...) -> thread,
|
create: <A..., R...>(f: (A...) -> R...) -> thread,
|
||||||
|
@ -382,6 +179,10 @@ declare coroutine: {
|
||||||
close: @checked (co: thread) -> (boolean, any)
|
close: @checked (co: thread) -> (boolean, any)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
)BUILTIN_SRC";
|
||||||
|
|
||||||
|
static const std::string kBuiltinDefinitionTableSrc = R"BUILTIN_SRC(
|
||||||
|
|
||||||
declare table: {
|
declare table: {
|
||||||
concat: <V>(t: {V}, sep: string?, i: number?, j: number?) -> string,
|
concat: <V>(t: {V}, sep: string?, i: number?, j: number?) -> string,
|
||||||
insert: (<V>(t: {V}, value: V) -> ()) & (<V>(t: {V}, pos: number, value: V) -> ()),
|
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,
|
isfrozen: <K, V>(t: {[K]: V}) -> boolean,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
)BUILTIN_SRC";
|
||||||
|
|
||||||
|
static const std::string kBuiltinDefinitionDebugSrc = R"BUILTIN_SRC(
|
||||||
|
|
||||||
declare debug: {
|
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),
|
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: {
|
declare utf8: {
|
||||||
char: @checked (...number) -> string,
|
char: @checked (...number) -> string,
|
||||||
charpattern: string,
|
charpattern: string,
|
||||||
|
@ -418,10 +227,9 @@ declare utf8: {
|
||||||
offset: @checked (s: string, n: number?, i: 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.
|
)BUILTIN_SRC";
|
||||||
declare function unpack<V>(tab: {V}, i: number?, j: number?): ...V
|
|
||||||
|
|
||||||
|
|
||||||
|
static const std::string kBuiltinDefinitionBufferSrc = R"BUILTIN_SRC(
|
||||||
--- Buffer API
|
--- Buffer API
|
||||||
declare buffer: {
|
declare buffer: {
|
||||||
create: @checked (size: number) -> buffer,
|
create: @checked (size: number) -> buffer,
|
||||||
|
@ -448,17 +256,53 @@ declare buffer: {
|
||||||
writef64: @checked (b: buffer, offset: number, value: number) -> (),
|
writef64: @checked (b: buffer, offset: number, value: number) -> (),
|
||||||
readstring: @checked (b: buffer, offset: number, count: number) -> string,
|
readstring: @checked (b: buffer, offset: number, count: number) -> string,
|
||||||
writestring: @checked (b: buffer, offset: number, value: string, count: number?) -> (),
|
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";
|
)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
|
-- While vector would have been better represented as a built-in primitive type, type solver extern type handling covers most of the properties
|
||||||
declare class vector end
|
declare extern type vector with
|
||||||
|
x: number
|
||||||
|
y: number
|
||||||
|
z: number
|
||||||
|
end
|
||||||
|
|
||||||
declare vector: {
|
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,
|
magnitude: @checked (vec: vector) -> number,
|
||||||
normalize: @checked (vec: vector) -> vector,
|
normalize: @checked (vec: vector) -> vector,
|
||||||
cross: @checked (vec1: vector, vec2: vector) -> vector,
|
cross: @checked (vec1: vector, vec2: vector) -> vector,
|
||||||
|
@ -480,10 +324,132 @@ declare vector: {
|
||||||
|
|
||||||
std::string getBuiltinDefinitionSource()
|
std::string getBuiltinDefinitionSource()
|
||||||
{
|
{
|
||||||
std::string result = FFlag::LuauMathMap ? kBuiltinDefinitionLuaSrcChecked : kBuiltinDefinitionLuaSrcChecked_DEPRECATED;
|
std::string result = kBuiltinDefinitionBaseSrc;
|
||||||
|
|
||||||
if (FFlag::LuauVectorDefinitions)
|
result += kBuiltinDefinitionBit32Src;
|
||||||
result += kBuiltinDefinitionVectorSrc;
|
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;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
LUAU_FASTFLAGVARIABLE(DebugLuauLogSimplification)
|
LUAU_FASTFLAGVARIABLE(DebugLuauLogSimplification)
|
||||||
|
LUAU_FASTFLAGVARIABLE(DebugLuauLogSimplificationToDot)
|
||||||
LUAU_FASTFLAGVARIABLE(DebugLuauExtraEqSatSanityChecks)
|
LUAU_FASTFLAGVARIABLE(DebugLuauExtraEqSatSanityChecks)
|
||||||
|
|
||||||
namespace Luau::EqSatSimplification
|
namespace Luau::EqSatSimplification
|
||||||
|
@ -91,18 +92,24 @@ size_t TTable::Hash::operator()(const TTable& value) const
|
||||||
return hash;
|
return hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t StringCache::add(std::string_view s)
|
StringId StringCache::add(std::string_view s)
|
||||||
{
|
{
|
||||||
size_t hash = std::hash<std::string_view>()(s);
|
/* Important subtlety: This use of DenseHashMap<std::string_view, StringId>
|
||||||
if (uint32_t* it = strings.find(hash))
|
* is okay because std::hash<std::string_view> works solely on the bytes
|
||||||
|
* referred by the string_view.
|
||||||
|
*
|
||||||
|
* In other words, two string views which contain the same bytes will have
|
||||||
|
* the same hash whether or not their addresses are the same.
|
||||||
|
*/
|
||||||
|
if (StringId* it = strings.find(s))
|
||||||
return *it;
|
return *it;
|
||||||
|
|
||||||
char* storage = static_cast<char*>(allocator.allocate(s.size()));
|
char* storage = static_cast<char*>(allocator.allocate(s.size()));
|
||||||
memcpy(storage, s.data(), s.size());
|
memcpy(storage, s.data(), s.size());
|
||||||
|
|
||||||
uint32_t result = uint32_t(views.size());
|
StringId result = StringId(views.size());
|
||||||
views.emplace_back(storage, s.size());
|
views.emplace_back(storage, s.size());
|
||||||
strings[hash] = result;
|
strings[s] = result;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -142,6 +149,61 @@ static bool isTerminal(const EType& node)
|
||||||
node.get<TNever>() || node.get<TNoRefine>();
|
node.get<TNever>() || node.get<TNoRefine>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool areTerminalAndDefinitelyDisjoint(const EType& lhs, const EType& rhs)
|
||||||
|
{
|
||||||
|
// If either node is non-terminal, then we early exit: we're not going to
|
||||||
|
// do a state space search for whether something like:
|
||||||
|
// (A | B | C | D) & (E | F | G | H)
|
||||||
|
// ... is a disjoint intersection.
|
||||||
|
if (!isTerminal(lhs) || !isTerminal(rhs))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Special case some types that aren't strict, disjoint subsets.
|
||||||
|
if (lhs.get<TTopClass>() || lhs.get<TClass>())
|
||||||
|
return !(rhs.get<TTopClass>() || rhs.get<TClass>());
|
||||||
|
|
||||||
|
// Handling strings / booleans: these are the types for which we
|
||||||
|
// expect something like:
|
||||||
|
//
|
||||||
|
// "foo" & ~"bar"
|
||||||
|
//
|
||||||
|
// ... to simplify to "foo".
|
||||||
|
if (lhs.get<TString>())
|
||||||
|
return !(rhs.get<TString>() || rhs.get<SString>());
|
||||||
|
|
||||||
|
if (lhs.get<TBoolean>())
|
||||||
|
return !(rhs.get<TBoolean>() || rhs.get<SBoolean>());
|
||||||
|
|
||||||
|
if (auto lhsSString = lhs.get<SString>())
|
||||||
|
{
|
||||||
|
auto rhsSString = rhs.get<SString>();
|
||||||
|
if (!rhsSString)
|
||||||
|
return !rhs.get<TString>();
|
||||||
|
return lhsSString->value() != rhsSString->value();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto lhsSBoolean = lhs.get<SBoolean>())
|
||||||
|
{
|
||||||
|
auto rhsSBoolean = rhs.get<SBoolean>();
|
||||||
|
if (!rhsSBoolean)
|
||||||
|
return !rhs.get<TBoolean>();
|
||||||
|
return lhsSBoolean->value() != rhsSBoolean->value();
|
||||||
|
}
|
||||||
|
|
||||||
|
// At this point:
|
||||||
|
// - We know both nodes are terminal
|
||||||
|
// - We know that the LHS is not any boolean, string, or class
|
||||||
|
// At this point, we have two classes of checks left:
|
||||||
|
// - Whether the two enodes are exactly the same set (now that the static
|
||||||
|
// sets have been covered).
|
||||||
|
// - Whether one of the enodes is a large semantic set such as TAny,
|
||||||
|
// TUnknown, or TError.
|
||||||
|
return !(
|
||||||
|
lhs.index() == rhs.index() || lhs.get<TUnknown>() || rhs.get<TUnknown>() || lhs.get<TAny>() || rhs.get<TAny>() || lhs.get<TNoRefine>() ||
|
||||||
|
rhs.get<TNoRefine>() || lhs.get<TError>() || rhs.get<TError>() || lhs.get<TOpaque>() || rhs.get<TOpaque>()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
static bool isTerminal(const EGraph& egraph, Id eclass)
|
static bool isTerminal(const EGraph& egraph, Id eclass)
|
||||||
{
|
{
|
||||||
const auto& nodes = egraph[eclass].nodes;
|
const auto& nodes = egraph[eclass].nodes;
|
||||||
|
@ -150,7 +212,7 @@ static bool isTerminal(const EGraph& egraph, Id eclass)
|
||||||
nodes.end(),
|
nodes.end(),
|
||||||
[](auto& a)
|
[](auto& a)
|
||||||
{
|
{
|
||||||
return isTerminal(a);
|
return isTerminal(a.node);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -268,9 +330,9 @@ Id toId(
|
||||||
return egraph.add(TOpaque{ty});
|
return egraph.add(TOpaque{ty});
|
||||||
else if (get<FunctionType>(ty))
|
else if (get<FunctionType>(ty))
|
||||||
return egraph.add(TFunction{ty});
|
return egraph.add(TFunction{ty});
|
||||||
else if (ty == builtinTypes->classType)
|
else if (ty == builtinTypes->externType)
|
||||||
return egraph.add(TTopClass{});
|
return egraph.add(TTopClass{});
|
||||||
else if (get<ClassType>(ty))
|
else if (get<ExternType>(ty))
|
||||||
return egraph.add(TClass{ty});
|
return egraph.add(TClass{ty});
|
||||||
else if (get<AnyType>(ty))
|
else if (get<AnyType>(ty))
|
||||||
return egraph.add(TAny{});
|
return egraph.add(TAny{});
|
||||||
|
@ -334,11 +396,32 @@ Id toId(
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(tfun->packArguments.empty());
|
LUAU_ASSERT(tfun->packArguments.empty());
|
||||||
|
|
||||||
|
if (tfun->userFuncName)
|
||||||
|
{
|
||||||
|
// TODO: User defined type functions are pseudo-effectful: error
|
||||||
|
// reporting is done via the `print` statement, so running a
|
||||||
|
// UDTF multiple times may end up double erroring. egraphs
|
||||||
|
// currently may induce type functions to be reduced multiple
|
||||||
|
// times. We should probably opt _not_ to process user defined
|
||||||
|
// type functions at all.
|
||||||
|
return egraph.add(TOpaque{ty});
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<Id> parts;
|
std::vector<Id> parts;
|
||||||
|
parts.reserve(tfun->typeArguments.size());
|
||||||
for (TypeId part : tfun->typeArguments)
|
for (TypeId part : tfun->typeArguments)
|
||||||
parts.push_back(toId(egraph, builtinTypes, mappingIdToClass, typeToMappingId, boundNodes, strings, part));
|
parts.push_back(toId(egraph, builtinTypes, mappingIdToClass, typeToMappingId, boundNodes, strings, part));
|
||||||
|
|
||||||
return cache(egraph.add(TTypeFun{tfun->function.get(), std::move(parts)}));
|
// This looks sily, but we're making a copy of the specific
|
||||||
|
// `TypeFunctionInstanceType` outside of the provided arena so that
|
||||||
|
// we can access the members without fear of the specific TFIT being
|
||||||
|
// overwritten with a bound type.
|
||||||
|
return cache(egraph.add(TTypeFun{
|
||||||
|
std::make_shared<const TypeFunctionInstanceType>(
|
||||||
|
tfun->function, tfun->typeArguments, tfun->packArguments, tfun->userFuncName, tfun->userFuncData
|
||||||
|
),
|
||||||
|
std::move(parts)
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
else if (get<NoRefineType>(ty))
|
else if (get<NoRefineType>(ty))
|
||||||
return egraph.add(TNoRefine{});
|
return egraph.add(TNoRefine{});
|
||||||
|
@ -398,7 +481,7 @@ static size_t computeCost(std::unordered_map<Id, size_t>& bestNodes, const EGrap
|
||||||
if (auto it = costs.find(id); it != costs.end())
|
if (auto it = costs.find(id); it != costs.end())
|
||||||
return it->second;
|
return it->second;
|
||||||
|
|
||||||
const std::vector<EType>& nodes = egraph[id].nodes;
|
const std::vector<Node<EType>>& nodes = egraph[id].nodes;
|
||||||
|
|
||||||
size_t minCost = std::numeric_limits<size_t>::max();
|
size_t minCost = std::numeric_limits<size_t>::max();
|
||||||
size_t bestNode = std::numeric_limits<size_t>::max();
|
size_t bestNode = std::numeric_limits<size_t>::max();
|
||||||
|
@ -415,7 +498,7 @@ static size_t computeCost(std::unordered_map<Id, size_t>& bestNodes, const EGrap
|
||||||
// First, quickly scan for a terminal type. If we can find one, it is obviously the best.
|
// First, quickly scan for a terminal type. If we can find one, it is obviously the best.
|
||||||
for (size_t index = 0; index < nodes.size(); ++index)
|
for (size_t index = 0; index < nodes.size(); ++index)
|
||||||
{
|
{
|
||||||
if (isTerminal(nodes[index]))
|
if (isTerminal(nodes[index].node))
|
||||||
{
|
{
|
||||||
minCost = 1;
|
minCost = 1;
|
||||||
bestNode = index;
|
bestNode = index;
|
||||||
|
@ -467,44 +550,44 @@ static size_t computeCost(std::unordered_map<Id, size_t>& bestNodes, const EGrap
|
||||||
{
|
{
|
||||||
const auto& node = nodes[index];
|
const auto& node = nodes[index];
|
||||||
|
|
||||||
if (node.get<TBound>())
|
if (node.node.get<TBound>())
|
||||||
updateCost(BOUND_PENALTY, index); // TODO: This could probably be an assert now that we don't need rewrite rules to handle TBound.
|
updateCost(BOUND_PENALTY, index); // TODO: This could probably be an assert now that we don't need rewrite rules to handle TBound.
|
||||||
else if (node.get<TFunction>())
|
else if (node.node.get<TFunction>())
|
||||||
{
|
{
|
||||||
minCost = 1;
|
minCost = 1;
|
||||||
bestNode = index;
|
bestNode = index;
|
||||||
}
|
}
|
||||||
else if (auto tbl = node.get<TTable>())
|
else if (auto tbl = node.node.get<TTable>())
|
||||||
{
|
{
|
||||||
// TODO: We could make the penalty a parameter to computeChildren.
|
// TODO: We could make the penalty a parameter to computeChildren.
|
||||||
std::optional<size_t> maybeCost = computeChildren(tbl->operands(), minCost);
|
std::optional<size_t> maybeCost = computeChildren(tbl->operands(), minCost);
|
||||||
if (maybeCost)
|
if (maybeCost)
|
||||||
updateCost(TABLE_TYPE_PENALTY + *maybeCost, index);
|
updateCost(TABLE_TYPE_PENALTY + *maybeCost, index);
|
||||||
}
|
}
|
||||||
else if (node.get<TImportedTable>())
|
else if (node.node.get<TImportedTable>())
|
||||||
{
|
{
|
||||||
minCost = IMPORTED_TABLE_PENALTY;
|
minCost = IMPORTED_TABLE_PENALTY;
|
||||||
bestNode = index;
|
bestNode = index;
|
||||||
}
|
}
|
||||||
else if (auto u = node.get<Union>())
|
else if (auto u = node.node.get<Union>())
|
||||||
{
|
{
|
||||||
std::optional<size_t> maybeCost = computeChildren(u->operands(), minCost);
|
std::optional<size_t> maybeCost = computeChildren(u->operands(), minCost);
|
||||||
if (maybeCost)
|
if (maybeCost)
|
||||||
updateCost(SET_TYPE_PENALTY + *maybeCost, index);
|
updateCost(SET_TYPE_PENALTY + *maybeCost, index);
|
||||||
}
|
}
|
||||||
else if (auto i = node.get<Intersection>())
|
else if (auto i = node.node.get<Intersection>())
|
||||||
{
|
{
|
||||||
std::optional<size_t> maybeCost = computeChildren(i->operands(), minCost);
|
std::optional<size_t> maybeCost = computeChildren(i->operands(), minCost);
|
||||||
if (maybeCost)
|
if (maybeCost)
|
||||||
updateCost(SET_TYPE_PENALTY + *maybeCost, index);
|
updateCost(SET_TYPE_PENALTY + *maybeCost, index);
|
||||||
}
|
}
|
||||||
else if (auto negation = node.get<Negation>())
|
else if (auto negation = node.node.get<Negation>())
|
||||||
{
|
{
|
||||||
std::optional<size_t> maybeCost = computeChildren(negation->operands(), minCost);
|
std::optional<size_t> maybeCost = computeChildren(negation->operands(), minCost);
|
||||||
if (maybeCost)
|
if (maybeCost)
|
||||||
updateCost(NEGATION_PENALTY + *maybeCost, index);
|
updateCost(NEGATION_PENALTY + *maybeCost, index);
|
||||||
}
|
}
|
||||||
else if (auto tfun = node.get<TTypeFun>())
|
else if (auto tfun = node.node.get<TTypeFun>())
|
||||||
{
|
{
|
||||||
std::optional<size_t> maybeCost = computeChildren(tfun->operands(), minCost);
|
std::optional<size_t> maybeCost = computeChildren(tfun->operands(), minCost);
|
||||||
if (maybeCost)
|
if (maybeCost)
|
||||||
|
@ -573,28 +656,34 @@ TypeId flattenTableNode(
|
||||||
// If a TTable is its own basis, it must be the case that some other
|
// If a TTable is its own basis, it must be the case that some other
|
||||||
// node on this eclass is a TImportedTable. Let's use that.
|
// node on this eclass is a TImportedTable. Let's use that.
|
||||||
|
|
||||||
|
bool found = false;
|
||||||
|
|
||||||
for (size_t i = 0; i < eclass.nodes.size(); ++i)
|
for (size_t i = 0; i < eclass.nodes.size(); ++i)
|
||||||
{
|
{
|
||||||
if (eclass.nodes[i].get<TImportedTable>())
|
if (eclass.nodes[i].node.get<TImportedTable>())
|
||||||
{
|
{
|
||||||
|
found = true;
|
||||||
index = i;
|
index = i;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we couldn't find one, we don't know what to do. Use ErrorType.
|
if (!found)
|
||||||
LUAU_ASSERT(0);
|
{
|
||||||
return builtinTypes->errorType;
|
// If we couldn't find one, we don't know what to do. Use ErrorType.
|
||||||
|
LUAU_ASSERT(0);
|
||||||
|
return builtinTypes->errorType;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto& node = eclass.nodes[index];
|
const auto& node = eclass.nodes[index];
|
||||||
if (const TTable* ttable = node.get<TTable>())
|
if (const TTable* ttable = node.node.get<TTable>())
|
||||||
{
|
{
|
||||||
stack.push_back(ttable);
|
stack.push_back(ttable);
|
||||||
id = ttable->getBasis();
|
id = ttable->getBasis();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
else if (const TImportedTable* ti = node.get<TImportedTable>())
|
else if (const TImportedTable* ti = node.node.get<TImportedTable>())
|
||||||
{
|
{
|
||||||
importedTable = ti;
|
importedTable = ti;
|
||||||
break;
|
break;
|
||||||
|
@ -621,7 +710,8 @@ TypeId flattenTableNode(
|
||||||
StringId propName = t->propNames[i];
|
StringId propName = t->propNames[i];
|
||||||
const Id propType = t->propTypes()[i];
|
const Id propType = t->propTypes()[i];
|
||||||
|
|
||||||
resultTable.props[strings.asString(propName)] = Property{fromId(egraph, strings, builtinTypes, arena, bestNodes, seen, newTypeFunctions, propType)};
|
resultTable.props[strings.asString(propName)] =
|
||||||
|
Property{fromId(egraph, strings, builtinTypes, arena, bestNodes, seen, newTypeFunctions, propType)};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -645,7 +735,7 @@ TypeId fromId(
|
||||||
size_t index = bestNodes.at(rootId);
|
size_t index = bestNodes.at(rootId);
|
||||||
LUAU_ASSERT(index <= egraph[rootId].nodes.size());
|
LUAU_ASSERT(index <= egraph[rootId].nodes.size());
|
||||||
|
|
||||||
const EType& node = egraph[rootId].nodes[index];
|
const EType& node = egraph[rootId].nodes[index].node;
|
||||||
|
|
||||||
if (node.get<TNil>())
|
if (node.get<TNil>())
|
||||||
return builtinTypes->nilType;
|
return builtinTypes->nilType;
|
||||||
|
@ -662,7 +752,7 @@ TypeId fromId(
|
||||||
else if (node.get<TTopTable>())
|
else if (node.get<TTopTable>())
|
||||||
return builtinTypes->tableType;
|
return builtinTypes->tableType;
|
||||||
else if (node.get<TTopClass>())
|
else if (node.get<TTopClass>())
|
||||||
return builtinTypes->classType;
|
return builtinTypes->externType;
|
||||||
else if (node.get<TBuffer>())
|
else if (node.get<TBuffer>())
|
||||||
return builtinTypes->bufferType;
|
return builtinTypes->bufferType;
|
||||||
else if (auto opaque = node.get<TOpaque>())
|
else if (auto opaque = node.get<TOpaque>())
|
||||||
|
@ -702,7 +792,20 @@ TypeId fromId(
|
||||||
if (parts.empty())
|
if (parts.empty())
|
||||||
return builtinTypes->neverType;
|
return builtinTypes->neverType;
|
||||||
else if (parts.size() == 1)
|
else if (parts.size() == 1)
|
||||||
return fromId(egraph, strings, builtinTypes, arena, bestNodes, seen, newTypeFunctions, parts[0]);
|
{
|
||||||
|
TypeId placeholder = arena->addType(BlockedType{});
|
||||||
|
seen[rootId] = placeholder;
|
||||||
|
auto result = fromId(egraph, strings, builtinTypes, arena, bestNodes, seen, newTypeFunctions, parts[0]);
|
||||||
|
if (follow(result) == placeholder)
|
||||||
|
{
|
||||||
|
emplaceType<GenericType>(asMutable(placeholder), "EGRAPH-SINGLETON-CYCLE");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
emplaceType<BoundType>(asMutable(placeholder), result);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
TypeId res = arena->addType(BlockedType{});
|
TypeId res = arena->addType(BlockedType{});
|
||||||
|
@ -767,7 +870,11 @@ TypeId fromId(
|
||||||
for (Id part : tfun->operands())
|
for (Id part : tfun->operands())
|
||||||
args.push_back(fromId(egraph, strings, builtinTypes, arena, bestNodes, seen, newTypeFunctions, part));
|
args.push_back(fromId(egraph, strings, builtinTypes, arena, bestNodes, seen, newTypeFunctions, part));
|
||||||
|
|
||||||
asMutable(res)->ty.emplace<TypeFunctionInstanceType>(*tfun->value(), std::move(args));
|
auto oldInstance = tfun->value();
|
||||||
|
|
||||||
|
asMutable(res)->ty.emplace<TypeFunctionInstanceType>(
|
||||||
|
oldInstance->function, std::move(args), std::vector<TypePackId>(), oldInstance->userFuncName, oldInstance->userFuncData
|
||||||
|
);
|
||||||
|
|
||||||
newTypeFunctions.push_back(res);
|
newTypeFunctions.push_back(res);
|
||||||
|
|
||||||
|
@ -847,12 +954,20 @@ std::string mkDesc(
|
||||||
const int RULE_PADDING = 35;
|
const int RULE_PADDING = 35;
|
||||||
const std::string rulePadding(std::max<size_t>(0, RULE_PADDING - rule.size()), ' ');
|
const std::string rulePadding(std::max<size_t>(0, RULE_PADDING - rule.size()), ' ');
|
||||||
const std::string fromIdStr = ""; // "(" + std::to_string(uint32_t(from)) + ") ";
|
const std::string fromIdStr = ""; // "(" + std::to_string(uint32_t(from)) + ") ";
|
||||||
const std::string toIdStr = ""; // "(" + std::to_string(uint32_t(to)) + ") ";
|
const std::string toIdStr = ""; // "(" + std::to_string(uint32_t(to)) + ") ";
|
||||||
|
|
||||||
return rule + ":" + rulePadding + fromIdStr + toString(fromTy, opts) + " <=> " + toIdStr + toString(toTy, opts);
|
return rule + ":" + rulePadding + fromIdStr + toString(fromTy, opts) + " <=> " + toIdStr + toString(toTy, opts);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string mkDesc(EGraph& egraph, const StringCache& strings, NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, Id from, Id to, const std::string& rule)
|
std::string mkDesc(
|
||||||
|
EGraph& egraph,
|
||||||
|
const StringCache& strings,
|
||||||
|
NotNull<TypeArena> arena,
|
||||||
|
NotNull<BuiltinTypes> builtinTypes,
|
||||||
|
Id from,
|
||||||
|
Id to,
|
||||||
|
const std::string& rule
|
||||||
|
)
|
||||||
{
|
{
|
||||||
if (!FFlag::DebugLuauLogSimplification)
|
if (!FFlag::DebugLuauLogSimplification)
|
||||||
return "";
|
return "";
|
||||||
|
@ -892,7 +1007,7 @@ static std::string getNodeName(const StringCache& strings, const EType& node)
|
||||||
return "\xe2\x88\xa9";
|
return "\xe2\x88\xa9";
|
||||||
else if (auto cls = node.get<TClass>())
|
else if (auto cls = node.get<TClass>())
|
||||||
{
|
{
|
||||||
const ClassType* ct = get<ClassType>(cls->value());
|
const ExternType* ct = get<ExternType>(cls->value());
|
||||||
LUAU_ASSERT(ct);
|
LUAU_ASSERT(ct);
|
||||||
return ct->name;
|
return ct->name;
|
||||||
}
|
}
|
||||||
|
@ -905,7 +1020,7 @@ static std::string getNodeName(const StringCache& strings, const EType& node)
|
||||||
else if (node.get<TNever>())
|
else if (node.get<TNever>())
|
||||||
return "never";
|
return "never";
|
||||||
else if (auto tfun = node.get<TTypeFun>())
|
else if (auto tfun = node.get<TTypeFun>())
|
||||||
return "tfun " + tfun->value()->name;
|
return "tfun " + tfun->value()->function->name;
|
||||||
else if (node.get<Negation>())
|
else if (node.get<Negation>())
|
||||||
return "~";
|
return "~";
|
||||||
else if (node.get<Invalid>())
|
else if (node.get<Invalid>())
|
||||||
|
@ -927,8 +1042,9 @@ std::string toDot(const StringCache& strings, const EGraph& egraph)
|
||||||
|
|
||||||
for (const auto& [id, eclass] : egraph.getAllClasses())
|
for (const auto& [id, eclass] : egraph.getAllClasses())
|
||||||
{
|
{
|
||||||
for (const auto& node : eclass.nodes)
|
for (const auto& n : eclass.nodes)
|
||||||
{
|
{
|
||||||
|
const EType& node = n.node;
|
||||||
if (!node.operands().empty())
|
if (!node.operands().empty())
|
||||||
populated.insert(id);
|
populated.insert(id);
|
||||||
for (Id op : node.operands())
|
for (Id op : node.operands())
|
||||||
|
@ -949,7 +1065,7 @@ std::string toDot(const StringCache& strings, const EGraph& egraph)
|
||||||
|
|
||||||
for (size_t index = 0; index < eclass.nodes.size(); ++index)
|
for (size_t index = 0; index < eclass.nodes.size(); ++index)
|
||||||
{
|
{
|
||||||
const auto& node = eclass.nodes[index];
|
const auto& node = eclass.nodes[index].node;
|
||||||
|
|
||||||
const std::string label = getNodeName(strings, node);
|
const std::string label = getNodeName(strings, node);
|
||||||
const std::string nodeName = "n" + std::to_string(uint32_t(id)) + "_" + std::to_string(index);
|
const std::string nodeName = "n" + std::to_string(uint32_t(id)) + "_" + std::to_string(index);
|
||||||
|
@ -964,7 +1080,7 @@ std::string toDot(const StringCache& strings, const EGraph& egraph)
|
||||||
{
|
{
|
||||||
for (size_t index = 0; index < eclass.nodes.size(); ++index)
|
for (size_t index = 0; index < eclass.nodes.size(); ++index)
|
||||||
{
|
{
|
||||||
const auto& node = eclass.nodes[index];
|
const auto& node = eclass.nodes[index].node;
|
||||||
|
|
||||||
const std::string label = getNodeName(strings, node);
|
const std::string label = getNodeName(strings, node);
|
||||||
const std::string nodeName = "n" + std::to_string(uint32_t(egraph.find(id))) + "_" + std::to_string(index);
|
const std::string nodeName = "n" + std::to_string(uint32_t(egraph.find(id))) + "_" + std::to_string(index);
|
||||||
|
@ -1000,7 +1116,7 @@ static Tag const* isTag(const EGraph& egraph, Id id)
|
||||||
{
|
{
|
||||||
for (const auto& node : egraph[id].nodes)
|
for (const auto& node : egraph[id].nodes)
|
||||||
{
|
{
|
||||||
if (auto n = isTag<Tag>(node))
|
if (auto n = isTag<Tag>(node.node))
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
@ -1036,7 +1152,7 @@ protected:
|
||||||
{
|
{
|
||||||
for (const auto& node : (*egraph)[id].nodes)
|
for (const auto& node : (*egraph)[id].nodes)
|
||||||
{
|
{
|
||||||
if (auto n = node.get<Tag>())
|
if (auto n = node.node.get<Tag>())
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
@ -1061,12 +1177,12 @@ enum SubclassRelationship
|
||||||
|
|
||||||
static SubclassRelationship relateClasses(const TClass* leftClass, const TClass* rightClass)
|
static SubclassRelationship relateClasses(const TClass* leftClass, const TClass* rightClass)
|
||||||
{
|
{
|
||||||
const ClassType* leftClassType = Luau::get<ClassType>(leftClass->value());
|
const ExternType* leftExternType = Luau::get<ExternType>(leftClass->value());
|
||||||
const ClassType* rightClassType = Luau::get<ClassType>(rightClass->value());
|
const ExternType* rightExternType = Luau::get<ExternType>(rightClass->value());
|
||||||
|
|
||||||
if (isSubclass(leftClassType, rightClassType))
|
if (isSubclass(leftExternType, rightExternType))
|
||||||
return RightSuper;
|
return RightSuper;
|
||||||
else if (isSubclass(rightClassType, leftClassType))
|
else if (isSubclass(rightExternType, leftExternType))
|
||||||
return LeftSuper;
|
return LeftSuper;
|
||||||
else
|
else
|
||||||
return Unrelated;
|
return Unrelated;
|
||||||
|
@ -1224,8 +1340,10 @@ const EType* findSubtractableClass(const EGraph& egraph, std::unordered_set<Id>&
|
||||||
const EType* bestUnion = nullptr;
|
const EType* bestUnion = nullptr;
|
||||||
std::optional<size_t> unionSize;
|
std::optional<size_t> unionSize;
|
||||||
|
|
||||||
for (const auto& node : egraph[id].nodes)
|
for (const auto& n : egraph[id].nodes)
|
||||||
{
|
{
|
||||||
|
const EType& node = n.node;
|
||||||
|
|
||||||
if (isTerminal(node))
|
if (isTerminal(node))
|
||||||
return &node;
|
return &node;
|
||||||
|
|
||||||
|
@ -1341,14 +1459,14 @@ bool subtract(EGraph& egraph, CanonicalizedType& ct, Id part)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Id fromCanonicalized(EGraph& egraph, CanonicalizedType& ct)
|
static std::pair<Id, size_t> fromCanonicalized(EGraph& egraph, CanonicalizedType& ct)
|
||||||
{
|
{
|
||||||
if (ct.isUnknown())
|
if (ct.isUnknown())
|
||||||
{
|
{
|
||||||
if (ct.errorPart)
|
if (ct.errorPart)
|
||||||
return egraph.add(TAny{});
|
return {egraph.add(TAny{}), 1};
|
||||||
else
|
else
|
||||||
return egraph.add(TUnknown{});
|
return {egraph.add(TUnknown{}), 1};
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<Id> parts;
|
std::vector<Id> parts;
|
||||||
|
@ -1386,7 +1504,12 @@ Id fromCanonicalized(EGraph& egraph, CanonicalizedType& ct)
|
||||||
parts.insert(parts.end(), ct.functionParts.begin(), ct.functionParts.end());
|
parts.insert(parts.end(), ct.functionParts.begin(), ct.functionParts.end());
|
||||||
parts.insert(parts.end(), ct.otherParts.begin(), ct.otherParts.end());
|
parts.insert(parts.end(), ct.otherParts.begin(), ct.otherParts.end());
|
||||||
|
|
||||||
return mkUnion(egraph, std::move(parts));
|
std::sort(parts.begin(), parts.end());
|
||||||
|
auto it = std::unique(parts.begin(), parts.end());
|
||||||
|
parts.erase(it, parts.end());
|
||||||
|
|
||||||
|
const size_t size = parts.size();
|
||||||
|
return {mkUnion(egraph, std::move(parts)), size};
|
||||||
}
|
}
|
||||||
|
|
||||||
void addChildren(const EGraph& egraph, const EType* enode, VecDeque<Id>& worklist)
|
void addChildren(const EGraph& egraph, const EType* enode, VecDeque<Id>& worklist)
|
||||||
|
@ -1432,7 +1555,7 @@ const Tag* Simplifier::isTag(Id id) const
|
||||||
{
|
{
|
||||||
for (const auto& node : get(id).nodes)
|
for (const auto& node : get(id).nodes)
|
||||||
{
|
{
|
||||||
if (const Tag* ty = node.get<Tag>())
|
if (const Tag* ty = node.node.get<Tag>())
|
||||||
return ty;
|
return ty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1466,6 +1589,16 @@ void Simplifier::subst(Id from, Id to, const std::string& ruleName, const std::u
|
||||||
substs.emplace_back(from, to, desc);
|
substs.emplace_back(from, to, desc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Simplifier::subst(Id from, size_t boringIndex, Id to, const std::string& ruleName, const std::unordered_map<Id, size_t>& forceNodes)
|
||||||
|
{
|
||||||
|
std::string desc;
|
||||||
|
if (FFlag::DebugLuauLogSimplification)
|
||||||
|
desc = mkDesc(egraph, stringCache, arena, builtinTypes, from, to, forceNodes, ruleName);
|
||||||
|
|
||||||
|
egraph.markBoring(from, boringIndex);
|
||||||
|
substs.emplace_back(from, to, desc);
|
||||||
|
}
|
||||||
|
|
||||||
void Simplifier::unionClasses(std::vector<Id>& hereParts, Id there)
|
void Simplifier::unionClasses(std::vector<Id>& hereParts, Id there)
|
||||||
{
|
{
|
||||||
if (1 == hereParts.size() && isTag<TTopClass>(hereParts[0]))
|
if (1 == hereParts.size() && isTag<TTopClass>(hereParts[0]))
|
||||||
|
@ -1516,9 +1649,12 @@ void Simplifier::simplifyUnion(Id id)
|
||||||
for (Id part : u->operands())
|
for (Id part : u->operands())
|
||||||
unionWithType(egraph, canonicalized, find(part));
|
unionWithType(egraph, canonicalized, find(part));
|
||||||
|
|
||||||
Id resultId = fromCanonicalized(egraph, canonicalized);
|
const auto [resultId, newSize] = fromCanonicalized(egraph, canonicalized);
|
||||||
|
|
||||||
subst(id, resultId, "simplifyUnion", {{id, unionIndex}});
|
if (newSize < u->operands().size())
|
||||||
|
subst(id, unionIndex, resultId, "simplifyUnion", {{id, unionIndex}});
|
||||||
|
else
|
||||||
|
subst(id, resultId, "simplifyUnion", {{id, unionIndex}});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1551,11 +1687,6 @@ std::optional<EType> intersectOne(EGraph& egraph, Id hereId, const EType* hereNo
|
||||||
thereNode->get<Intersection>() || thereNode->get<Negation>() || hereNode->get<TOpaque>() || thereNode->get<TOpaque>())
|
thereNode->get<Intersection>() || thereNode->get<Negation>() || hereNode->get<TOpaque>() || thereNode->get<TOpaque>())
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
|
|
||||||
if (hereNode->get<TAny>())
|
|
||||||
return *thereNode;
|
|
||||||
if (thereNode->get<TAny>())
|
|
||||||
return *hereNode;
|
|
||||||
|
|
||||||
if (hereNode->get<TUnknown>())
|
if (hereNode->get<TUnknown>())
|
||||||
return *thereNode;
|
return *thereNode;
|
||||||
if (thereNode->get<TUnknown>())
|
if (thereNode->get<TUnknown>())
|
||||||
|
@ -1731,7 +1862,7 @@ void Simplifier::uninhabitedIntersection(Id id)
|
||||||
const auto& partNodes = egraph[partId].nodes;
|
const auto& partNodes = egraph[partId].nodes;
|
||||||
for (size_t partIndex = 0; partIndex < partNodes.size(); ++partIndex)
|
for (size_t partIndex = 0; partIndex < partNodes.size(); ++partIndex)
|
||||||
{
|
{
|
||||||
const EType& N = partNodes[partIndex];
|
const EType& N = partNodes[partIndex].node;
|
||||||
if (std::optional<EType> intersection = intersectOne(egraph, accumulator, &accumulatorNode, partId, &N))
|
if (std::optional<EType> intersection = intersectOne(egraph, accumulator, &accumulatorNode, partId, &N))
|
||||||
{
|
{
|
||||||
if (isTag<TNever>(*intersection))
|
if (isTag<TNever>(*intersection))
|
||||||
|
@ -1754,9 +1885,14 @@ void Simplifier::uninhabitedIntersection(Id id)
|
||||||
if ((unsimplified.empty() || !isTag<TUnknown>(accumulator)) && find(accumulator) != id)
|
if ((unsimplified.empty() || !isTag<TUnknown>(accumulator)) && find(accumulator) != id)
|
||||||
unsimplified.push_back(accumulator);
|
unsimplified.push_back(accumulator);
|
||||||
|
|
||||||
|
const bool isSmaller = unsimplified.size() < parts.size();
|
||||||
|
|
||||||
const Id result = mkIntersection(egraph, std::move(unsimplified));
|
const Id result = mkIntersection(egraph, std::move(unsimplified));
|
||||||
|
|
||||||
subst(id, result, "uninhabitedIntersection", {{id, index}});
|
if (isSmaller)
|
||||||
|
subst(id, index, result, "uninhabitedIntersection", {{id, index}});
|
||||||
|
else
|
||||||
|
subst(id, result, "uninhabitedIntersection", {{id, index}});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1787,14 +1923,19 @@ void Simplifier::intersectWithNegatedClass(Id id)
|
||||||
const auto& iNodes = egraph[iId].nodes;
|
const auto& iNodes = egraph[iId].nodes;
|
||||||
for (size_t iIndex = 0; iIndex < iNodes.size(); ++iIndex)
|
for (size_t iIndex = 0; iIndex < iNodes.size(); ++iIndex)
|
||||||
{
|
{
|
||||||
const EType& iNode = iNodes[iIndex];
|
const EType& iNode = iNodes[iIndex].node;
|
||||||
if (isTag<TNil>(iNode) || isTag<TBoolean>(iNode) || isTag<TNumber>(iNode) || isTag<TString>(iNode) || isTag<TThread>(iNode) ||
|
if (isTag<TNil>(iNode) || isTag<TBoolean>(iNode) || isTag<TNumber>(iNode) || isTag<TString>(iNode) || isTag<TThread>(iNode) ||
|
||||||
isTag<TTopFunction>(iNode) ||
|
isTag<TTopFunction>(iNode) ||
|
||||||
// isTag<TTopTable>(iNode) || // I'm not sure about this one.
|
// isTag<TTopTable>(iNode) || // I'm not sure about this one.
|
||||||
isTag<SBoolean>(iNode) || isTag<SString>(iNode) || isTag<TFunction>(iNode) || isTag<TNever>(iNode))
|
isTag<SBoolean>(iNode) || isTag<SString>(iNode) || isTag<TFunction>(iNode) || isTag<TNever>(iNode))
|
||||||
{
|
{
|
||||||
// eg string & ~SomeClass
|
// eg string & ~SomeClass
|
||||||
subst(id, iId, "intersectClassWithNegatedClass", {{id, intersectionIndex}, {iId, iIndex}, {jId, negationIndex}, {negated, negatedClassIndex}});
|
subst(
|
||||||
|
id,
|
||||||
|
iId,
|
||||||
|
"intersectClassWithNegatedClass",
|
||||||
|
{{id, intersectionIndex}, {iId, iIndex}, {jId, negationIndex}, {negated, negatedClassIndex}}
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1802,27 +1943,37 @@ void Simplifier::intersectWithNegatedClass(Id id)
|
||||||
{
|
{
|
||||||
switch (relateClasses(class_, negatedClass))
|
switch (relateClasses(class_, negatedClass))
|
||||||
{
|
{
|
||||||
case LeftSuper:
|
case LeftSuper:
|
||||||
// eg Instance & ~Part
|
// eg Instance & ~Part
|
||||||
// This cannot be meaningfully reduced.
|
// This cannot be meaningfully reduced.
|
||||||
continue;
|
continue;
|
||||||
case RightSuper:
|
case RightSuper:
|
||||||
subst(id, egraph.add(TNever{}), "intersectClassWithNegatedClass", {{id, intersectionIndex}, {iId, iIndex}, {jId, negationIndex}, {negated, negatedClassIndex}});
|
subst(
|
||||||
return;
|
id,
|
||||||
case Unrelated:
|
egraph.add(TNever{}),
|
||||||
// Part & ~Folder == Part
|
"intersectClassWithNegatedClass",
|
||||||
|
{{id, intersectionIndex}, {iId, iIndex}, {jId, negationIndex}, {negated, negatedClassIndex}}
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
case Unrelated:
|
||||||
|
// Part & ~Folder == Part
|
||||||
|
{
|
||||||
|
std::vector<Id> newParts;
|
||||||
|
newParts.reserve(intersection->operands().size() - 1);
|
||||||
|
for (Id part : intersection->operands())
|
||||||
{
|
{
|
||||||
std::vector<Id> newParts;
|
if (part != jId)
|
||||||
newParts.reserve(intersection->operands().size() - 1);
|
newParts.push_back(part);
|
||||||
for (Id part : intersection->operands())
|
|
||||||
{
|
|
||||||
if (part != jId)
|
|
||||||
newParts.push_back(part);
|
|
||||||
}
|
|
||||||
|
|
||||||
Id substId = egraph.add(Intersection{newParts.begin(), newParts.end()});
|
|
||||||
subst(id, substId, "intersectClassWithNegatedClass", {{id, intersectionIndex}, {iId, iIndex}, {jId, negationIndex}, {negated, negatedClassIndex}});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Id substId = mkIntersection(egraph, newParts);
|
||||||
|
subst(
|
||||||
|
id,
|
||||||
|
substId,
|
||||||
|
"intersectClassWithNegatedClass",
|
||||||
|
{{id, intersectionIndex}, {iId, iIndex}, {jId, negationIndex}, {negated, negatedClassIndex}}
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1838,6 +1989,74 @@ void Simplifier::intersectWithNegatedClass(Id id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Simplifier::intersectWithNegatedAtom(Id id)
|
||||||
|
{
|
||||||
|
// Let I and ~J be two arbitrary distinct operands of an intersection where
|
||||||
|
// I and J are terminal but are not type variables. (free, generic, or
|
||||||
|
// otherwise opaque)
|
||||||
|
//
|
||||||
|
// If I and J are equal, then the whole intersection is equivalent to never.
|
||||||
|
//
|
||||||
|
// If I and J are inequal, then J & ~I == J
|
||||||
|
|
||||||
|
for (const auto [intersection, intersectionIndex] : Query<Intersection>(&egraph, id))
|
||||||
|
{
|
||||||
|
const Slice<const Id>& intersectionOperands = intersection->operands();
|
||||||
|
for (size_t i = 0; i < intersectionOperands.size(); ++i)
|
||||||
|
{
|
||||||
|
for (const auto [negation, negationIndex] : Query<Negation>(&egraph, intersectionOperands[i]))
|
||||||
|
{
|
||||||
|
for (size_t negationOperandIndex = 0; negationOperandIndex < egraph[negation->operands()[0]].nodes.size(); ++negationOperandIndex)
|
||||||
|
{
|
||||||
|
const EType* negationOperand = &egraph[negation->operands()[0]].nodes[negationOperandIndex].node;
|
||||||
|
if (!isTerminal(*negationOperand) || negationOperand->get<TOpaque>())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
for (size_t j = 0; j < intersectionOperands.size(); ++j)
|
||||||
|
{
|
||||||
|
if (j == i)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
for (size_t jNodeIndex = 0; jNodeIndex < egraph[intersectionOperands[j]].nodes.size(); ++jNodeIndex)
|
||||||
|
{
|
||||||
|
const EType* jNode = &egraph[intersectionOperands[j]].nodes[jNodeIndex].node;
|
||||||
|
if (!isTerminal(*jNode) || jNode->get<TOpaque>())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (*negationOperand == *jNode)
|
||||||
|
{
|
||||||
|
// eg "Hello" & ~"Hello"
|
||||||
|
// or boolean & ~boolean
|
||||||
|
subst(
|
||||||
|
id,
|
||||||
|
egraph.add(TNever{}),
|
||||||
|
"intersectWithNegatedAtom",
|
||||||
|
{{id, intersectionIndex}, {intersectionOperands[i], negationIndex}, {intersectionOperands[j], jNodeIndex}}
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (areTerminalAndDefinitelyDisjoint(*jNode, *negationOperand))
|
||||||
|
{
|
||||||
|
// eg "Hello" & ~"World"
|
||||||
|
// or boolean & ~string
|
||||||
|
std::vector<Id> newOperands(intersectionOperands.begin(), intersectionOperands.end());
|
||||||
|
newOperands.erase(newOperands.begin() + std::vector<Id>::difference_type(i));
|
||||||
|
|
||||||
|
subst(
|
||||||
|
id,
|
||||||
|
mkIntersection(egraph, std::move(newOperands)),
|
||||||
|
"intersectWithNegatedAtom",
|
||||||
|
{{id, intersectionIndex}, {intersectionOperands[i], negationIndex}, {intersectionOperands[j], jNodeIndex}}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Simplifier::intersectWithNoRefine(Id id)
|
void Simplifier::intersectWithNoRefine(Id id)
|
||||||
{
|
{
|
||||||
for (const auto pair : Query<Intersection>(&egraph, id))
|
for (const auto pair : Query<Intersection>(&egraph, id))
|
||||||
|
@ -2002,7 +2221,7 @@ void Simplifier::expandNegation(Id id)
|
||||||
if (!ok)
|
if (!ok)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
subst(id, fromCanonicalized(egraph, canonicalized), "expandNegation", {{id, index}});
|
subst(id, fromCanonicalized(egraph, canonicalized).first, "expandNegation", {{id, index}});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2159,7 +2378,7 @@ void Simplifier::intersectTableProperty(Id id)
|
||||||
|
|
||||||
subst(
|
subst(
|
||||||
id,
|
id,
|
||||||
egraph.add(Intersection{std::move(newIntersectionParts)}),
|
mkIntersection(egraph, std::move(newIntersectionParts)),
|
||||||
"intersectTableProperty",
|
"intersectTableProperty",
|
||||||
{{id, intersectionIndex}, {iId, table1Index}, {jId, table2Index}}
|
{{id, intersectionIndex}, {iId, table1Index}, {jId, table2Index}}
|
||||||
);
|
);
|
||||||
|
@ -2249,7 +2468,7 @@ void Simplifier::builtinTypeFunctions(Id id)
|
||||||
if (args.size() != 2)
|
if (args.size() != 2)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
const std::string& name = tfun->value()->name;
|
const std::string& name = tfun->value()->function->name;
|
||||||
if (name == "add" || name == "sub" || name == "mul" || name == "div" || name == "idiv" || name == "pow" || name == "mod")
|
if (name == "add" || name == "sub" || name == "mul" || name == "div" || name == "idiv" || name == "pow" || name == "mod")
|
||||||
{
|
{
|
||||||
if (isTag<TNumber>(args[0]) && isTag<TNumber>(args[1]))
|
if (isTag<TNumber>(args[0]) && isTag<TNumber>(args[1]))
|
||||||
|
@ -2271,15 +2490,43 @@ void Simplifier::iffyTypeFunctions(Id id)
|
||||||
{
|
{
|
||||||
const Slice<const Id>& args = tfun->operands();
|
const Slice<const Id>& args = tfun->operands();
|
||||||
|
|
||||||
const std::string& name = tfun->value()->name;
|
const std::string& name = tfun->value()->function->name;
|
||||||
|
|
||||||
if (name == "union")
|
if (name == "union")
|
||||||
subst(id, add(Union{std::vector(args.begin(), args.end())}), "iffyTypeFunctions", {{id, index}});
|
subst(id, add(Union{std::vector(args.begin(), args.end())}), "iffyTypeFunctions", {{id, index}});
|
||||||
else if (name == "intersect" || name == "refine")
|
else if (name == "intersect")
|
||||||
subst(id, add(Intersection{std::vector(args.begin(), args.end())}), "iffyTypeFunctions", {{id, index}});
|
subst(id, add(Intersection{std::vector(args.begin(), args.end())}), "iffyTypeFunctions", {{id, index}});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Replace instances of `lt<X, Y>` and `le<X, Y>` when either X or Y is `number`
|
||||||
|
// or `string` with `boolean`. Lua semantics are that if we see the expression:
|
||||||
|
//
|
||||||
|
// x < y
|
||||||
|
//
|
||||||
|
// ... we error if `x` and `y` don't have the same type. We know that for
|
||||||
|
// `string` and `number`, comparisons will always return a boolean. So if either
|
||||||
|
// of the arguments to `lt<>` are equivalent to `number` or `string`, then the
|
||||||
|
// type is effectively `boolean`: either the other type is equivalent, in which
|
||||||
|
// case we eval to `boolean`, or we diverge (raise an error).
|
||||||
|
void Simplifier::strictMetamethods(Id id)
|
||||||
|
{
|
||||||
|
for (const auto [tfun, index] : Query<TTypeFun>(&egraph, id))
|
||||||
|
{
|
||||||
|
const Slice<const Id>& args = tfun->operands();
|
||||||
|
|
||||||
|
const std::string& name = tfun->value()->function->name;
|
||||||
|
|
||||||
|
if (!(name == "lt" || name == "le") || args.size() != 2)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (isTag<TNumber>(args[0]) || isTag<TString>(args[0]) || isTag<TNumber>(args[1]) || isTag<TString>(args[1]))
|
||||||
|
{
|
||||||
|
subst(id, add(TBoolean{}), __FUNCTION__, {{id, index}});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void deleteSimplifier(Simplifier* s)
|
static void deleteSimplifier(Simplifier* s)
|
||||||
{
|
{
|
||||||
delete s;
|
delete s;
|
||||||
|
@ -2307,6 +2554,7 @@ std::optional<EqSatSimplificationResult> eqSatSimplify(NotNull<Simplifier> simpl
|
||||||
&Simplifier::simplifyUnion,
|
&Simplifier::simplifyUnion,
|
||||||
&Simplifier::uninhabitedIntersection,
|
&Simplifier::uninhabitedIntersection,
|
||||||
&Simplifier::intersectWithNegatedClass,
|
&Simplifier::intersectWithNegatedClass,
|
||||||
|
&Simplifier::intersectWithNegatedAtom,
|
||||||
&Simplifier::intersectWithNoRefine,
|
&Simplifier::intersectWithNoRefine,
|
||||||
&Simplifier::cyclicIntersectionOfUnion,
|
&Simplifier::cyclicIntersectionOfUnion,
|
||||||
&Simplifier::cyclicUnionOfIntersection,
|
&Simplifier::cyclicUnionOfIntersection,
|
||||||
|
@ -2317,6 +2565,7 @@ std::optional<EqSatSimplificationResult> eqSatSimplify(NotNull<Simplifier> simpl
|
||||||
&Simplifier::unneededTableModification,
|
&Simplifier::unneededTableModification,
|
||||||
&Simplifier::builtinTypeFunctions,
|
&Simplifier::builtinTypeFunctions,
|
||||||
&Simplifier::iffyTypeFunctions,
|
&Simplifier::iffyTypeFunctions,
|
||||||
|
&Simplifier::strictMetamethods,
|
||||||
};
|
};
|
||||||
|
|
||||||
std::unordered_set<Id> seen;
|
std::unordered_set<Id> seen;
|
||||||
|
@ -2327,7 +2576,7 @@ std::optional<EqSatSimplificationResult> eqSatSimplify(NotNull<Simplifier> simpl
|
||||||
int count = 0;
|
int count = 0;
|
||||||
const int MAX_COUNT = 1000;
|
const int MAX_COUNT = 1000;
|
||||||
|
|
||||||
if (FFlag::DebugLuauLogSimplification)
|
if (FFlag::DebugLuauLogSimplificationToDot)
|
||||||
std::ofstream("begin.dot") << toDot(simplifier->stringCache, simplifier->egraph);
|
std::ofstream("begin.dot") << toDot(simplifier->stringCache, simplifier->egraph);
|
||||||
|
|
||||||
auto& egraph = simplifier->egraph;
|
auto& egraph = simplifier->egraph;
|
||||||
|
@ -2370,9 +2619,9 @@ std::optional<EqSatSimplificationResult> eqSatSimplify(NotNull<Simplifier> simpl
|
||||||
// try to run any rules on it.
|
// try to run any rules on it.
|
||||||
bool shouldAbort = false;
|
bool shouldAbort = false;
|
||||||
|
|
||||||
for (const EType& enode : egraph[id].nodes)
|
for (const auto& enode : egraph[id].nodes)
|
||||||
{
|
{
|
||||||
if (isTerminal(enode))
|
if (isTerminal(enode.node))
|
||||||
{
|
{
|
||||||
shouldAbort = true;
|
shouldAbort = true;
|
||||||
break;
|
break;
|
||||||
|
@ -2382,8 +2631,8 @@ std::optional<EqSatSimplificationResult> eqSatSimplify(NotNull<Simplifier> simpl
|
||||||
if (shouldAbort)
|
if (shouldAbort)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
for (const EType& enode : egraph[id].nodes)
|
for (const auto& enode : egraph[id].nodes)
|
||||||
addChildren(egraph, &enode, worklist);
|
addChildren(egraph, &enode.node, worklist);
|
||||||
|
|
||||||
for (Simplifier::RewriteRuleFn rule : rules)
|
for (Simplifier::RewriteRuleFn rule : rules)
|
||||||
(simplifier.get()->*rule)(id);
|
(simplifier.get()->*rule)(id);
|
||||||
|
@ -2409,11 +2658,11 @@ std::optional<EqSatSimplificationResult> eqSatSimplify(NotNull<Simplifier> simpl
|
||||||
|
|
||||||
++count;
|
++count;
|
||||||
|
|
||||||
if (FFlag::DebugLuauLogSimplification)
|
if (FFlag::DebugLuauLogSimplification && isFresh)
|
||||||
{
|
std::cout << "count=" << std::setw(3) << count << "\t" << subst.desc << '\n';
|
||||||
if (isFresh)
|
|
||||||
std::cout << "count=" << std::setw(3) << count << "\t" << subst.desc << '\n';
|
|
||||||
|
|
||||||
|
if (FFlag::DebugLuauLogSimplificationToDot)
|
||||||
|
{
|
||||||
std::string filename = format("step%03d.dot", count);
|
std::string filename = format("step%03d.dot", count);
|
||||||
std::ofstream(filename) << toDot(simplifier->stringCache, egraph);
|
std::ofstream(filename) << toDot(simplifier->stringCache, egraph);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
#include "Luau/StringUtils.h"
|
#include "Luau/StringUtils.h"
|
||||||
#include "Luau/ToString.h"
|
#include "Luau/ToString.h"
|
||||||
#include "Luau/Type.h"
|
#include "Luau/Type.h"
|
||||||
|
#include "Luau/TypeChecker2.h"
|
||||||
#include "Luau/TypeFunction.h"
|
#include "Luau/TypeFunction.h"
|
||||||
|
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
@ -17,6 +18,7 @@
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
|
|
||||||
LUAU_FASTINTVARIABLE(LuauIndentTypeMismatchMaxTypeLength, 10)
|
LUAU_FASTINTVARIABLE(LuauIndentTypeMismatchMaxTypeLength, 10)
|
||||||
|
LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
|
||||||
|
|
||||||
static std::string wrongNumberOfArgsString(
|
static std::string wrongNumberOfArgsString(
|
||||||
size_t expectedCount,
|
size_t expectedCount,
|
||||||
|
@ -68,7 +70,7 @@ namespace Luau
|
||||||
{
|
{
|
||||||
|
|
||||||
// this list of binary operator type functions is used for better stringification of type functions errors
|
// this list of binary operator type functions is used for better stringification of type functions errors
|
||||||
static const std::unordered_map<std::string, const char*> kBinaryOps{
|
static const std::unordered_map<std::string, const char*> DEPRECATED_kBinaryOps{
|
||||||
{"add", "+"},
|
{"add", "+"},
|
||||||
{"sub", "-"},
|
{"sub", "-"},
|
||||||
{"mul", "*"},
|
{"mul", "*"},
|
||||||
|
@ -84,12 +86,27 @@ static const std::unordered_map<std::string, const char*> kBinaryOps{
|
||||||
{"eq", "== or ~="}
|
{"eq", "== or ~="}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static const std::unordered_map<std::string, const char*> kBinaryOps{
|
||||||
|
{"add", "+"},
|
||||||
|
{"sub", "-"},
|
||||||
|
{"mul", "*"},
|
||||||
|
{"div", "/"},
|
||||||
|
{"idiv", "//"},
|
||||||
|
{"pow", "^"},
|
||||||
|
{"mod", "%"},
|
||||||
|
{"concat", ".."},
|
||||||
|
{"lt", "< or >="},
|
||||||
|
{"le", "<= or >"},
|
||||||
|
{"eq", "== or ~="}
|
||||||
|
};
|
||||||
|
|
||||||
// this list of unary operator type functions is used for better stringification of type functions errors
|
// this list of unary operator type functions is used for better stringification of type functions errors
|
||||||
static const std::unordered_map<std::string, const char*> kUnaryOps{{"unm", "-"}, {"len", "#"}, {"not", "not"}};
|
static const std::unordered_map<std::string, const char*> kUnaryOps{{"unm", "-"}, {"len", "#"}, {"not", "not"}};
|
||||||
|
|
||||||
// this list of type functions will receive a special error indicating that the user should file a bug on the GitHub repository
|
// this list of type functions will receive a special error indicating that the user should file a bug on the GitHub repository
|
||||||
// putting a type function in this list indicates that it is expected to _always_ reduce
|
// putting a type function in this list indicates that it is expected to _always_ reduce
|
||||||
static const std::unordered_set<std::string> kUnreachableTypeFunctions{"refine", "singleton", "union", "intersect"};
|
static const std::unordered_set<std::string> DEPRECATED_kUnreachableTypeFunctions{"refine", "singleton", "union", "intersect"};
|
||||||
|
static const std::unordered_set<std::string> kUnreachableTypeFunctions{"refine", "singleton", "union", "intersect", "and", "or"};
|
||||||
|
|
||||||
struct ErrorConverter
|
struct ErrorConverter
|
||||||
{
|
{
|
||||||
|
@ -116,7 +133,10 @@ struct ErrorConverter
|
||||||
size_t luauIndentTypeMismatchMaxTypeLength = size_t(FInt::LuauIndentTypeMismatchMaxTypeLength);
|
size_t luauIndentTypeMismatchMaxTypeLength = size_t(FInt::LuauIndentTypeMismatchMaxTypeLength);
|
||||||
if (givenType.length() <= luauIndentTypeMismatchMaxTypeLength || wantedType.length() <= luauIndentTypeMismatchMaxTypeLength)
|
if (givenType.length() <= luauIndentTypeMismatchMaxTypeLength || wantedType.length() <= luauIndentTypeMismatchMaxTypeLength)
|
||||||
return "Type " + given + " could not be converted into " + wanted;
|
return "Type " + given + " could not be converted into " + wanted;
|
||||||
return "Type\n " + given + "\ncould not be converted into\n " + wanted;
|
if (FFlag::LuauImproveTypePathsInErrors)
|
||||||
|
return "Type\n\t" + given + "\ncould not be converted into\n\t" + wanted;
|
||||||
|
else
|
||||||
|
return "Type\n " + given + "\ncould not be converted into\n " + wanted;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (givenTypeName == wantedTypeName)
|
if (givenTypeName == wantedTypeName)
|
||||||
|
@ -183,7 +203,7 @@ struct ErrorConverter
|
||||||
TypeId t = follow(e.table);
|
TypeId t = follow(e.table);
|
||||||
if (get<TableType>(t))
|
if (get<TableType>(t))
|
||||||
return "Key '" + e.key + "' not found in table '" + Luau::toString(t) + "'";
|
return "Key '" + e.key + "' not found in table '" + Luau::toString(t) + "'";
|
||||||
else if (get<ClassType>(t))
|
else if (get<ExternType>(t))
|
||||||
return "Key '" + e.key + "' not found in class '" + Luau::toString(t) + "'";
|
return "Key '" + e.key + "' not found in class '" + Luau::toString(t) + "'";
|
||||||
else
|
else
|
||||||
return "Type '" + Luau::toString(e.table) + "' does not have key '" + e.key + "'";
|
return "Type '" + Luau::toString(e.table) + "' does not have key '" + e.key + "'";
|
||||||
|
@ -351,7 +371,7 @@ struct ErrorConverter
|
||||||
std::string s = "Key '" + e.key + "' not found in ";
|
std::string s = "Key '" + e.key + "' not found in ";
|
||||||
|
|
||||||
TypeId t = follow(e.table);
|
TypeId t = follow(e.table);
|
||||||
if (get<ClassType>(t))
|
if (get<ExternType>(t))
|
||||||
s += "class";
|
s += "class";
|
||||||
else
|
else
|
||||||
s += "table";
|
s += "table";
|
||||||
|
@ -382,8 +402,8 @@ struct ErrorConverter
|
||||||
std::optional<TypeId> metatable;
|
std::optional<TypeId> metatable;
|
||||||
if (const MetatableType* mtType = get<MetatableType>(type))
|
if (const MetatableType* mtType = get<MetatableType>(type))
|
||||||
metatable = mtType->metatable;
|
metatable = mtType->metatable;
|
||||||
else if (const ClassType* classType = get<ClassType>(type))
|
else if (const ExternType* externType = get<ExternType>(type))
|
||||||
metatable = classType->metatable;
|
metatable = externType->metatable;
|
||||||
|
|
||||||
if (!metatable)
|
if (!metatable)
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
|
@ -591,7 +611,7 @@ struct ErrorConverter
|
||||||
return ss;
|
return ss;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string operator()(const DynamicPropertyLookupOnClassesUnsafe& e) const
|
std::string operator()(const DynamicPropertyLookupOnExternTypesUnsafe& e) const
|
||||||
{
|
{
|
||||||
return "Attempting a dynamic property access on type '" + Luau::toString(e.ty) + "' is unsafe and may cause exceptions at runtime";
|
return "Attempting a dynamic property access on type '" + Luau::toString(e.ty) + "' is unsafe and may cause exceptions at runtime";
|
||||||
}
|
}
|
||||||
|
@ -601,7 +621,7 @@ struct ErrorConverter
|
||||||
auto tfit = get<TypeFunctionInstanceType>(e.ty);
|
auto tfit = get<TypeFunctionInstanceType>(e.ty);
|
||||||
LUAU_ASSERT(tfit); // Luau analysis has actually done something wrong if this type is not a type function.
|
LUAU_ASSERT(tfit); // Luau analysis has actually done something wrong if this type is not a type function.
|
||||||
if (!tfit)
|
if (!tfit)
|
||||||
return "Unexpected type " + Luau::toString(e.ty) + " flagged as an uninhabited type function.";
|
return "Internal error: Unexpected type " + Luau::toString(e.ty) + " flagged as an uninhabited type function.";
|
||||||
|
|
||||||
// unary operators
|
// unary operators
|
||||||
if (auto unaryString = kUnaryOps.find(tfit->function->name); unaryString != kUnaryOps.end())
|
if (auto unaryString = kUnaryOps.find(tfit->function->name); unaryString != kUnaryOps.end())
|
||||||
|
@ -638,7 +658,8 @@ struct ErrorConverter
|
||||||
}
|
}
|
||||||
|
|
||||||
// binary operators
|
// binary operators
|
||||||
if (auto binaryString = kBinaryOps.find(tfit->function->name); binaryString != kBinaryOps.end())
|
const auto binaryOps = FFlag::DebugLuauGreedyGeneralization ? kBinaryOps : DEPRECATED_kBinaryOps;
|
||||||
|
if (auto binaryString = binaryOps.find(tfit->function->name); binaryString != binaryOps.end())
|
||||||
{
|
{
|
||||||
std::string result = "Operator '" + std::string(binaryString->second) + "' could not be applied to operands of types ";
|
std::string result = "Operator '" + std::string(binaryString->second) + "' could not be applied to operands of types ";
|
||||||
|
|
||||||
|
@ -692,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" +
|
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.
|
// 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
|
std::string operator()(const NonStrictFunctionDefinitionError& e) const
|
||||||
{
|
{
|
||||||
return "Argument " + e.argument + " with type '" + toString(e.argumentType) + "' in function '" + e.functionName +
|
if (e.functionName.empty())
|
||||||
"' is used in a way that will run time error";
|
{
|
||||||
|
return "Argument " + e.argument + " with type '" + toString(e.argumentType) + "' is used in a way that will run time error";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return "Argument " + e.argument + " with type '" + toString(e.argumentType) + "' in function '" + e.functionName +
|
||||||
|
"' is used in a way that will run time error";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string operator()(const PropertyAccessViolation& e) const
|
std::string operator()(const PropertyAccessViolation& e) const
|
||||||
|
@ -791,6 +819,11 @@ struct ErrorConverter
|
||||||
return e.message;
|
return e.message;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string operator()(const ReservedIdentifier& e) const
|
||||||
|
{
|
||||||
|
return e.name + " cannot be used as an identifier for a type function or alias";
|
||||||
|
}
|
||||||
|
|
||||||
std::string operator()(const CannotAssignToNever& e) const
|
std::string operator()(const CannotAssignToNever& e) const
|
||||||
{
|
{
|
||||||
std::string result = "Cannot assign a value of type " + toString(e.rhsType) + " to a field of type never";
|
std::string result = "Cannot assign a value of type " + toString(e.rhsType) + " to a field of type never";
|
||||||
|
@ -1116,7 +1149,7 @@ bool TypePackMismatch::operator==(const TypePackMismatch& rhs) const
|
||||||
return *wantedTp == *rhs.wantedTp && *givenTp == *rhs.givenTp;
|
return *wantedTp == *rhs.wantedTp && *givenTp == *rhs.givenTp;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DynamicPropertyLookupOnClassesUnsafe::operator==(const DynamicPropertyLookupOnClassesUnsafe& rhs) const
|
bool DynamicPropertyLookupOnExternTypesUnsafe::operator==(const DynamicPropertyLookupOnExternTypesUnsafe& rhs) const
|
||||||
{
|
{
|
||||||
return ty == rhs.ty;
|
return ty == rhs.ty;
|
||||||
}
|
}
|
||||||
|
@ -1178,6 +1211,11 @@ bool UserDefinedTypeFunctionError::operator==(const UserDefinedTypeFunctionError
|
||||||
return message == rhs.message;
|
return message == rhs.message;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ReservedIdentifier::operator==(const ReservedIdentifier& rhs) const
|
||||||
|
{
|
||||||
|
return name == rhs.name;
|
||||||
|
}
|
||||||
|
|
||||||
bool CannotAssignToNever::operator==(const CannotAssignToNever& rhs) const
|
bool CannotAssignToNever::operator==(const CannotAssignToNever& rhs) const
|
||||||
{
|
{
|
||||||
if (cause.size() != rhs.cause.size())
|
if (cause.size() != rhs.cause.size())
|
||||||
|
@ -1353,7 +1391,7 @@ void copyError(T& e, TypeArena& destArena, CloneState& cloneState)
|
||||||
e.wantedTp = clone(e.wantedTp);
|
e.wantedTp = clone(e.wantedTp);
|
||||||
e.givenTp = clone(e.givenTp);
|
e.givenTp = clone(e.givenTp);
|
||||||
}
|
}
|
||||||
else if constexpr (std::is_same_v<T, DynamicPropertyLookupOnClassesUnsafe>)
|
else if constexpr (std::is_same_v<T, DynamicPropertyLookupOnExternTypesUnsafe>)
|
||||||
e.ty = clone(e.ty);
|
e.ty = clone(e.ty);
|
||||||
else if constexpr (std::is_same_v<T, UninhabitedTypeFunction>)
|
else if constexpr (std::is_same_v<T, UninhabitedTypeFunction>)
|
||||||
e.ty = clone(e.ty);
|
e.ty = clone(e.ty);
|
||||||
|
@ -1397,6 +1435,9 @@ void copyError(T& e, TypeArena& destArena, CloneState& cloneState)
|
||||||
for (auto& ty : e.cause)
|
for (auto& ty : e.cause)
|
||||||
ty = clone(ty);
|
ty = clone(ty);
|
||||||
}
|
}
|
||||||
|
else if constexpr (std::is_same_v<T, ReservedIdentifier>)
|
||||||
|
{
|
||||||
|
}
|
||||||
else
|
else
|
||||||
static_assert(always_false_v<T>, "Non-exhaustive type switch");
|
static_assert(always_false_v<T>, "Non-exhaustive type switch");
|
||||||
}
|
}
|
||||||
|
|
160
Analysis/src/FileResolver.cpp
Normal file
160
Analysis/src/FileResolver.cpp
Normal 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
|
@ -1,7 +1,6 @@
|
||||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
#include "Luau/Frontend.h"
|
#include "Luau/Frontend.h"
|
||||||
|
|
||||||
#include "Luau/AnyTypeSummary.h"
|
|
||||||
#include "Luau/BuiltinDefinitions.h"
|
#include "Luau/BuiltinDefinitions.h"
|
||||||
#include "Luau/Clone.h"
|
#include "Luau/Clone.h"
|
||||||
#include "Luau/Common.h"
|
#include "Luau/Common.h"
|
||||||
|
@ -13,6 +12,7 @@
|
||||||
#include "Luau/EqSatSimplification.h"
|
#include "Luau/EqSatSimplification.h"
|
||||||
#include "Luau/FileResolver.h"
|
#include "Luau/FileResolver.h"
|
||||||
#include "Luau/NonStrictTypeChecker.h"
|
#include "Luau/NonStrictTypeChecker.h"
|
||||||
|
#include "Luau/NotNull.h"
|
||||||
#include "Luau/Parser.h"
|
#include "Luau/Parser.h"
|
||||||
#include "Luau/Scope.h"
|
#include "Luau/Scope.h"
|
||||||
#include "Luau/StringUtils.h"
|
#include "Luau/StringUtils.h"
|
||||||
|
@ -38,18 +38,16 @@ LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
||||||
LUAU_FASTINT(LuauTarjanChildLimit)
|
LUAU_FASTINT(LuauTarjanChildLimit)
|
||||||
LUAU_FASTFLAG(LuauInferInNoCheckMode)
|
LUAU_FASTFLAG(LuauInferInNoCheckMode)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3)
|
LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauStoreCommentsForDefinitionFiles)
|
|
||||||
LUAU_FASTFLAG(LuauSolverV2)
|
LUAU_FASTFLAG(LuauSolverV2)
|
||||||
|
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauRethrowKnownExceptions, false)
|
||||||
|
LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
|
||||||
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson)
|
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson)
|
||||||
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJsonFile)
|
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJsonFile)
|
||||||
LUAU_FASTFLAGVARIABLE(DebugLuauForbidInternalTypes)
|
LUAU_FASTFLAGVARIABLE(DebugLuauForbidInternalTypes)
|
||||||
LUAU_FASTFLAGVARIABLE(DebugLuauForceStrictMode)
|
LUAU_FASTFLAGVARIABLE(DebugLuauForceStrictMode)
|
||||||
LUAU_FASTFLAGVARIABLE(DebugLuauForceNonStrictMode)
|
LUAU_FASTFLAGVARIABLE(DebugLuauForceNonStrictMode)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauUserDefinedTypeFunctionNoEvaluation)
|
|
||||||
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauRunCustomModuleChecks, false)
|
|
||||||
|
|
||||||
LUAU_FASTFLAG(StudioReportLuauAny2)
|
LUAU_FASTFLAG(LuauTypeFunResultInAutocomplete)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauStoreDFGOnModule2)
|
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
@ -79,6 +77,20 @@ struct BuildQueueItem
|
||||||
Frontend::Stats stats;
|
Frontend::Stats stats;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct BuildQueueWorkState
|
||||||
|
{
|
||||||
|
std::function<void(std::function<void()> task)> executeTask;
|
||||||
|
|
||||||
|
std::vector<BuildQueueItem> buildQueueItems;
|
||||||
|
|
||||||
|
std::mutex mtx;
|
||||||
|
std::condition_variable cv;
|
||||||
|
std::vector<size_t> readyQueueItems;
|
||||||
|
|
||||||
|
size_t processing = 0;
|
||||||
|
size_t remaining = 0;
|
||||||
|
};
|
||||||
|
|
||||||
std::optional<Mode> parseMode(const std::vector<HotComment>& hotcomments)
|
std::optional<Mode> parseMode(const std::vector<HotComment>& hotcomments)
|
||||||
{
|
{
|
||||||
for (const HotComment& hc : hotcomments)
|
for (const HotComment& hc : hotcomments)
|
||||||
|
@ -117,9 +129,9 @@ static void generateDocumentationSymbols(TypeId ty, const std::string& rootName)
|
||||||
prop.documentationSymbol = rootName + "." + name;
|
prop.documentationSymbol = rootName + "." + name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (ClassType* ctv = getMutable<ClassType>(ty))
|
else if (ExternType* etv = getMutable<ExternType>(ty))
|
||||||
{
|
{
|
||||||
for (auto& [name, prop] : ctv->props)
|
for (auto& [name, prop] : etv->props)
|
||||||
{
|
{
|
||||||
prop.documentationSymbol = rootName + "." + name;
|
prop.documentationSymbol = rootName + "." + name;
|
||||||
}
|
}
|
||||||
|
@ -136,7 +148,7 @@ static ParseResult parseSourceForModule(std::string_view source, Luau::SourceMod
|
||||||
sourceModule.root = parseResult.root;
|
sourceModule.root = parseResult.root;
|
||||||
sourceModule.mode = Mode::Definition;
|
sourceModule.mode = Mode::Definition;
|
||||||
|
|
||||||
if (FFlag::LuauStoreCommentsForDefinitionFiles && options.captureComments)
|
if (options.captureComments)
|
||||||
{
|
{
|
||||||
sourceModule.hotcomments = parseResult.hotcomments;
|
sourceModule.hotcomments = parseResult.hotcomments;
|
||||||
sourceModule.commentLocations = parseResult.commentLocations;
|
sourceModule.commentLocations = parseResult.commentLocations;
|
||||||
|
@ -443,20 +455,6 @@ CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOption
|
||||||
|
|
||||||
if (item.name == name)
|
if (item.name == name)
|
||||||
checkResult.lintResult = item.module->lintResult;
|
checkResult.lintResult = item.module->lintResult;
|
||||||
|
|
||||||
if (FFlag::StudioReportLuauAny2 && item.options.retainFullTypeGraphs)
|
|
||||||
{
|
|
||||||
if (item.module)
|
|
||||||
{
|
|
||||||
const SourceModule& sourceModule = *item.sourceModule;
|
|
||||||
if (sourceModule.mode == Luau::Mode::Strict)
|
|
||||||
{
|
|
||||||
item.module->ats.root = toString(sourceModule.root);
|
|
||||||
}
|
|
||||||
item.module->ats.rootSrc = sourceModule.root;
|
|
||||||
item.module->ats.traverse(item.module.get(), sourceModule.root, NotNull{&builtinTypes_});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return checkResult;
|
return checkResult;
|
||||||
|
@ -487,7 +485,8 @@ std::vector<ModuleName> Frontend::checkQueuedModules(
|
||||||
std::swap(currModuleQueue, moduleQueue);
|
std::swap(currModuleQueue, moduleQueue);
|
||||||
|
|
||||||
DenseHashSet<Luau::ModuleName> seen{{}};
|
DenseHashSet<Luau::ModuleName> seen{{}};
|
||||||
std::vector<BuildQueueItem> buildQueueItems;
|
|
||||||
|
std::shared_ptr<BuildQueueWorkState> state = std::make_shared<BuildQueueWorkState>();
|
||||||
|
|
||||||
for (const ModuleName& name : currModuleQueue)
|
for (const ModuleName& name : currModuleQueue)
|
||||||
{
|
{
|
||||||
|
@ -511,18 +510,18 @@ std::vector<ModuleName> Frontend::checkQueuedModules(
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
addBuildQueueItems(buildQueueItems, queue, cycleDetected, seen, frontendOptions);
|
addBuildQueueItems(state->buildQueueItems, queue, cycleDetected, seen, frontendOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (buildQueueItems.empty())
|
if (state->buildQueueItems.empty())
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
// We need a mapping from modules to build queue slots
|
// We need a mapping from modules to build queue slots
|
||||||
std::unordered_map<ModuleName, size_t> moduleNameToQueue;
|
std::unordered_map<ModuleName, size_t> moduleNameToQueue;
|
||||||
|
|
||||||
for (size_t i = 0; i < buildQueueItems.size(); i++)
|
for (size_t i = 0; i < state->buildQueueItems.size(); i++)
|
||||||
{
|
{
|
||||||
BuildQueueItem& item = buildQueueItems[i];
|
BuildQueueItem& item = state->buildQueueItems[i];
|
||||||
moduleNameToQueue[item.name] = i;
|
moduleNameToQueue[item.name] = i;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -535,67 +534,13 @@ std::vector<ModuleName> Frontend::checkQueuedModules(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
std::mutex mtx;
|
state->executeTask = executeTask;
|
||||||
std::condition_variable cv;
|
state->remaining = state->buildQueueItems.size();
|
||||||
std::vector<size_t> readyQueueItems;
|
|
||||||
|
|
||||||
size_t processing = 0;
|
// Record dependencies between modules
|
||||||
size_t remaining = buildQueueItems.size();
|
for (size_t i = 0; i < state->buildQueueItems.size(); i++)
|
||||||
|
|
||||||
auto itemTask = [&](size_t i)
|
|
||||||
{
|
{
|
||||||
BuildQueueItem& item = buildQueueItems[i];
|
BuildQueueItem& item = state->buildQueueItems[i];
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
checkBuildQueueItem(item);
|
|
||||||
}
|
|
||||||
catch (...)
|
|
||||||
{
|
|
||||||
item.exception = std::current_exception();
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
std::unique_lock guard(mtx);
|
|
||||||
readyQueueItems.push_back(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
cv.notify_one();
|
|
||||||
};
|
|
||||||
|
|
||||||
auto sendItemTask = [&](size_t i)
|
|
||||||
{
|
|
||||||
BuildQueueItem& item = buildQueueItems[i];
|
|
||||||
|
|
||||||
item.processing = true;
|
|
||||||
processing++;
|
|
||||||
|
|
||||||
executeTask(
|
|
||||||
[&itemTask, i]()
|
|
||||||
{
|
|
||||||
itemTask(i);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
auto sendCycleItemTask = [&]
|
|
||||||
{
|
|
||||||
for (size_t i = 0; i < buildQueueItems.size(); i++)
|
|
||||||
{
|
|
||||||
BuildQueueItem& item = buildQueueItems[i];
|
|
||||||
|
|
||||||
if (!item.processing)
|
|
||||||
{
|
|
||||||
sendItemTask(i);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// In a first pass, check modules that have no dependencies and record info of those modules that wait
|
|
||||||
for (size_t i = 0; i < buildQueueItems.size(); i++)
|
|
||||||
{
|
|
||||||
BuildQueueItem& item = buildQueueItems[i];
|
|
||||||
|
|
||||||
for (const ModuleName& dep : item.sourceNode->requireSet)
|
for (const ModuleName& dep : item.sourceNode->requireSet)
|
||||||
{
|
{
|
||||||
|
@ -605,41 +550,45 @@ std::vector<ModuleName> Frontend::checkQueuedModules(
|
||||||
{
|
{
|
||||||
item.dirtyDependencies++;
|
item.dirtyDependencies++;
|
||||||
|
|
||||||
buildQueueItems[moduleNameToQueue[dep]].reverseDeps.push_back(i);
|
state->buildQueueItems[moduleNameToQueue[dep]].reverseDeps.push_back(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.dirtyDependencies == 0)
|
|
||||||
sendItemTask(i);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Not a single item was found, a cycle in the graph was hit
|
// In the first pass, check all modules with no pending dependencies
|
||||||
if (processing == 0)
|
for (size_t i = 0; i < state->buildQueueItems.size(); i++)
|
||||||
sendCycleItemTask();
|
{
|
||||||
|
if (state->buildQueueItems[i].dirtyDependencies == 0)
|
||||||
|
sendQueueItemTask(state, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If not a single item was found, a cycle in the graph was hit
|
||||||
|
if (state->processing == 0)
|
||||||
|
sendQueueCycleItemTask(state);
|
||||||
|
|
||||||
std::vector<size_t> nextItems;
|
std::vector<size_t> nextItems;
|
||||||
std::optional<size_t> itemWithException;
|
std::optional<size_t> itemWithException;
|
||||||
bool cancelled = false;
|
bool cancelled = false;
|
||||||
|
|
||||||
while (remaining != 0)
|
while (state->remaining != 0)
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
std::unique_lock guard(mtx);
|
std::unique_lock guard(state->mtx);
|
||||||
|
|
||||||
// If nothing is ready yet, wait
|
// If nothing is ready yet, wait
|
||||||
cv.wait(
|
state->cv.wait(
|
||||||
guard,
|
guard,
|
||||||
[&readyQueueItems]
|
[state]
|
||||||
{
|
{
|
||||||
return !readyQueueItems.empty();
|
return !state->readyQueueItems.empty();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// Handle checked items
|
// Handle checked items
|
||||||
for (size_t i : readyQueueItems)
|
for (size_t i : state->readyQueueItems)
|
||||||
{
|
{
|
||||||
const BuildQueueItem& item = buildQueueItems[i];
|
const BuildQueueItem& item = state->buildQueueItems[i];
|
||||||
|
|
||||||
// If exception was thrown, stop adding new items and wait for processing items to complete
|
// If exception was thrown, stop adding new items and wait for processing items to complete
|
||||||
if (item.exception)
|
if (item.exception)
|
||||||
|
@ -656,7 +605,7 @@ std::vector<ModuleName> Frontend::checkQueuedModules(
|
||||||
// Notify items that were waiting for this dependency
|
// Notify items that were waiting for this dependency
|
||||||
for (size_t reverseDep : item.reverseDeps)
|
for (size_t reverseDep : item.reverseDeps)
|
||||||
{
|
{
|
||||||
BuildQueueItem& reverseDepItem = buildQueueItems[reverseDep];
|
BuildQueueItem& reverseDepItem = state->buildQueueItems[reverseDep];
|
||||||
|
|
||||||
LUAU_ASSERT(reverseDepItem.dirtyDependencies != 0);
|
LUAU_ASSERT(reverseDepItem.dirtyDependencies != 0);
|
||||||
reverseDepItem.dirtyDependencies--;
|
reverseDepItem.dirtyDependencies--;
|
||||||
|
@ -667,26 +616,26 @@ std::vector<ModuleName> Frontend::checkQueuedModules(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LUAU_ASSERT(processing >= readyQueueItems.size());
|
LUAU_ASSERT(state->processing >= state->readyQueueItems.size());
|
||||||
processing -= readyQueueItems.size();
|
state->processing -= state->readyQueueItems.size();
|
||||||
|
|
||||||
LUAU_ASSERT(remaining >= readyQueueItems.size());
|
LUAU_ASSERT(state->remaining >= state->readyQueueItems.size());
|
||||||
remaining -= readyQueueItems.size();
|
state->remaining -= state->readyQueueItems.size();
|
||||||
readyQueueItems.clear();
|
state->readyQueueItems.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (progress)
|
if (progress)
|
||||||
{
|
{
|
||||||
if (!progress(buildQueueItems.size() - remaining, buildQueueItems.size()))
|
if (!progress(state->buildQueueItems.size() - state->remaining, state->buildQueueItems.size()))
|
||||||
cancelled = true;
|
cancelled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Items cannot be submitted while holding the lock
|
// Items cannot be submitted while holding the lock
|
||||||
for (size_t i : nextItems)
|
for (size_t i : nextItems)
|
||||||
sendItemTask(i);
|
sendQueueItemTask(state, i);
|
||||||
nextItems.clear();
|
nextItems.clear();
|
||||||
|
|
||||||
if (processing == 0)
|
if (state->processing == 0)
|
||||||
{
|
{
|
||||||
// Typechecking might have been cancelled by user, don't return partial results
|
// Typechecking might have been cancelled by user, don't return partial results
|
||||||
if (cancelled)
|
if (cancelled)
|
||||||
|
@ -694,19 +643,19 @@ std::vector<ModuleName> Frontend::checkQueuedModules(
|
||||||
|
|
||||||
// We might have stopped because of a pending exception
|
// We might have stopped because of a pending exception
|
||||||
if (itemWithException)
|
if (itemWithException)
|
||||||
recordItemResult(buildQueueItems[*itemWithException]);
|
recordItemResult(state->buildQueueItems[*itemWithException]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we aren't done, but don't have anything processing, we hit a cycle
|
// If we aren't done, but don't have anything processing, we hit a cycle
|
||||||
if (remaining != 0 && processing == 0)
|
if (state->remaining != 0 && state->processing == 0)
|
||||||
sendCycleItemTask();
|
sendQueueCycleItemTask(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<ModuleName> checkedModules;
|
std::vector<ModuleName> checkedModules;
|
||||||
checkedModules.reserve(buildQueueItems.size());
|
checkedModules.reserve(state->buildQueueItems.size());
|
||||||
|
|
||||||
for (size_t i = 0; i < buildQueueItems.size(); i++)
|
for (size_t i = 0; i < state->buildQueueItems.size(); i++)
|
||||||
checkedModules.push_back(std::move(buildQueueItems[i].name));
|
checkedModules.push_back(std::move(state->buildQueueItems[i].name));
|
||||||
|
|
||||||
return checkedModules;
|
return checkedModules;
|
||||||
}
|
}
|
||||||
|
@ -744,6 +693,32 @@ std::optional<CheckResult> Frontend::getCheckResult(const ModuleName& name, bool
|
||||||
return checkResult;
|
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(
|
bool Frontend::parseGraph(
|
||||||
std::vector<ModuleName>& buildQueue,
|
std::vector<ModuleName>& buildQueue,
|
||||||
const ModuleName& root,
|
const ModuleName& root,
|
||||||
|
@ -792,6 +767,13 @@ bool Frontend::parseGraph(
|
||||||
topseen = Permanent;
|
topseen = Permanent;
|
||||||
|
|
||||||
buildQueue.push_back(top->name);
|
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
|
else
|
||||||
{
|
{
|
||||||
|
@ -969,7 +951,7 @@ void Frontend::checkBuildQueueItem(BuildQueueItem& item)
|
||||||
item.stats.timeCheck += duration;
|
item.stats.timeCheck += duration;
|
||||||
item.stats.filesStrict += 1;
|
item.stats.filesStrict += 1;
|
||||||
|
|
||||||
if (DFFlag::LuauRunCustomModuleChecks && item.options.customModuleCheck)
|
if (item.options.customModuleCheck)
|
||||||
item.options.customModuleCheck(sourceModule, *moduleForAutocomplete);
|
item.options.customModuleCheck(sourceModule, *moduleForAutocomplete);
|
||||||
|
|
||||||
item.module = moduleForAutocomplete;
|
item.module = moduleForAutocomplete;
|
||||||
|
@ -989,7 +971,7 @@ void Frontend::checkBuildQueueItem(BuildQueueItem& item)
|
||||||
item.stats.filesStrict += mode == Mode::Strict;
|
item.stats.filesStrict += mode == Mode::Strict;
|
||||||
item.stats.filesNonstrict += mode == Mode::Nonstrict;
|
item.stats.filesNonstrict += mode == Mode::Nonstrict;
|
||||||
|
|
||||||
if (DFFlag::LuauRunCustomModuleChecks && item.options.customModuleCheck)
|
if (item.options.customModuleCheck)
|
||||||
item.options.customModuleCheck(sourceModule, *module);
|
item.options.customModuleCheck(sourceModule, *module);
|
||||||
|
|
||||||
if (FFlag::LuauSolverV2 && mode == Mode::NoCheck)
|
if (FFlag::LuauSolverV2 && mode == Mode::NoCheck)
|
||||||
|
@ -1021,6 +1003,8 @@ void Frontend::checkBuildQueueItem(BuildQueueItem& item)
|
||||||
freeze(module->interfaceTypes);
|
freeze(module->interfaceTypes);
|
||||||
|
|
||||||
module->internalTypes.clear();
|
module->internalTypes.clear();
|
||||||
|
module->defArena.allocator.clear();
|
||||||
|
module->keyArena.allocator.clear();
|
||||||
|
|
||||||
module->astTypes.clear();
|
module->astTypes.clear();
|
||||||
module->astTypePacks.clear();
|
module->astTypePacks.clear();
|
||||||
|
@ -1074,17 +1058,35 @@ void Frontend::recordItemResult(const BuildQueueItem& item)
|
||||||
if (item.exception)
|
if (item.exception)
|
||||||
std::rethrow_exception(item.exception);
|
std::rethrow_exception(item.exception);
|
||||||
|
|
||||||
|
bool replacedModule = false;
|
||||||
if (item.options.forAutocomplete)
|
if (item.options.forAutocomplete)
|
||||||
{
|
{
|
||||||
moduleResolverForAutocomplete.setModule(item.name, item.module);
|
replacedModule = moduleResolverForAutocomplete.setModule(item.name, item.module);
|
||||||
item.sourceNode->dirtyModuleForAutocomplete = false;
|
item.sourceNode->dirtyModuleForAutocomplete = false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
moduleResolver.setModule(item.name, item.module);
|
replacedModule = moduleResolver.setModule(item.name, item.module);
|
||||||
item.sourceNode->dirtyModule = false;
|
item.sourceNode->dirtyModule = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (replacedModule)
|
||||||
|
{
|
||||||
|
LUAU_TIMETRACE_SCOPE("Frontend::invalidateDependentModules", "Frontend");
|
||||||
|
LUAU_TIMETRACE_ARGUMENT("name", item.name.c_str());
|
||||||
|
traverseDependents(
|
||||||
|
item.name,
|
||||||
|
[forAutocomplete = item.options.forAutocomplete](SourceNode& sourceNode)
|
||||||
|
{
|
||||||
|
bool traverseSubtree = !sourceNode.hasInvalidModuleDependency(forAutocomplete);
|
||||||
|
sourceNode.setInvalidModuleDependency(true, forAutocomplete);
|
||||||
|
return traverseSubtree;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
item.sourceNode->setInvalidModuleDependency(false, item.options.forAutocomplete);
|
||||||
|
|
||||||
stats.timeCheck += item.stats.timeCheck;
|
stats.timeCheck += item.stats.timeCheck;
|
||||||
stats.timeLint += item.stats.timeLint;
|
stats.timeLint += item.stats.timeLint;
|
||||||
|
|
||||||
|
@ -1092,6 +1094,72 @@ void Frontend::recordItemResult(const BuildQueueItem& item)
|
||||||
stats.filesNonstrict += item.stats.filesNonstrict;
|
stats.filesNonstrict += item.stats.filesNonstrict;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Frontend::performQueueItemTask(std::shared_ptr<BuildQueueWorkState> state, size_t itemPos)
|
||||||
|
{
|
||||||
|
BuildQueueItem& item = state->buildQueueItems[itemPos];
|
||||||
|
|
||||||
|
if (DFFlag::LuauRethrowKnownExceptions)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
checkBuildQueueItem(item);
|
||||||
|
}
|
||||||
|
catch (const Luau::InternalCompilerError&)
|
||||||
|
{
|
||||||
|
item.exception = std::current_exception();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
checkBuildQueueItem(item);
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
item.exception = std::current_exception();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::unique_lock guard(state->mtx);
|
||||||
|
state->readyQueueItems.push_back(itemPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
state->cv.notify_one();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Frontend::sendQueueItemTask(std::shared_ptr<BuildQueueWorkState> state, size_t itemPos)
|
||||||
|
{
|
||||||
|
BuildQueueItem& item = state->buildQueueItems[itemPos];
|
||||||
|
|
||||||
|
LUAU_ASSERT(!item.processing);
|
||||||
|
item.processing = true;
|
||||||
|
|
||||||
|
state->processing++;
|
||||||
|
|
||||||
|
state->executeTask(
|
||||||
|
[this, state, itemPos]()
|
||||||
|
{
|
||||||
|
performQueueItemTask(state, itemPos);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Frontend::sendQueueCycleItemTask(std::shared_ptr<BuildQueueWorkState> state)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < state->buildQueueItems.size(); i++)
|
||||||
|
{
|
||||||
|
BuildQueueItem& item = state->buildQueueItems[i];
|
||||||
|
|
||||||
|
if (!item.processing)
|
||||||
|
{
|
||||||
|
sendQueueItemTask(state, i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ScopePtr Frontend::getModuleEnvironment(const SourceModule& module, const Config& config, bool forAutocomplete) const
|
ScopePtr Frontend::getModuleEnvironment(const SourceModule& module, const Config& config, bool forAutocomplete) const
|
||||||
{
|
{
|
||||||
ScopePtr result;
|
ScopePtr result;
|
||||||
|
@ -1119,6 +1187,12 @@ ScopePtr Frontend::getModuleEnvironment(const SourceModule& module, const Config
|
||||||
return result;
|
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
|
bool Frontend::isDirty(const ModuleName& name, bool forAutocomplete) const
|
||||||
{
|
{
|
||||||
auto it = sourceNodes.find(name);
|
auto it = sourceNodes.find(name);
|
||||||
|
@ -1133,16 +1207,35 @@ bool Frontend::isDirty(const ModuleName& name, bool forAutocomplete) const
|
||||||
*/
|
*/
|
||||||
void Frontend::markDirty(const ModuleName& name, std::vector<ModuleName>* markedDirty)
|
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)
|
if (sourceNodes.count(name) == 0)
|
||||||
return;
|
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};
|
std::vector<ModuleName> queue{name};
|
||||||
|
|
||||||
while (!queue.empty())
|
while (!queue.empty())
|
||||||
|
@ -1153,22 +1246,10 @@ void Frontend::markDirty(const ModuleName& name, std::vector<ModuleName>* marked
|
||||||
LUAU_ASSERT(sourceNodes.count(next) > 0);
|
LUAU_ASSERT(sourceNodes.count(next) > 0);
|
||||||
SourceNode& sourceNode = *sourceNodes[next];
|
SourceNode& sourceNode = *sourceNodes[next];
|
||||||
|
|
||||||
if (markedDirty)
|
if (!processSubtree(sourceNode))
|
||||||
markedDirty->push_back(next);
|
|
||||||
|
|
||||||
if (sourceNode.dirtySourceModule && sourceNode.dirtyModule && sourceNode.dirtyModuleForAutocomplete)
|
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
sourceNode.dirtySourceModule = true;
|
const Set<ModuleName>& dependents = sourceNode.dependents;
|
||||||
sourceNode.dirtyModule = true;
|
|
||||||
sourceNode.dirtyModuleForAutocomplete = true;
|
|
||||||
|
|
||||||
if (0 == reverseDeps.count(next))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
sourceModules.erase(next);
|
|
||||||
|
|
||||||
const std::vector<ModuleName>& dependents = reverseDeps[next];
|
|
||||||
queue.insert(queue.end(), dependents.begin(), dependents.end());
|
queue.insert(queue.end(), dependents.begin(), dependents.end());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1196,6 +1277,7 @@ ModulePtr check(
|
||||||
NotNull<ModuleResolver> moduleResolver,
|
NotNull<ModuleResolver> moduleResolver,
|
||||||
NotNull<FileResolver> fileResolver,
|
NotNull<FileResolver> fileResolver,
|
||||||
const ScopePtr& parentScope,
|
const ScopePtr& parentScope,
|
||||||
|
const ScopePtr& typeFunctionScope,
|
||||||
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope,
|
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope,
|
||||||
FrontendOptions options,
|
FrontendOptions options,
|
||||||
TypeCheckLimits limits,
|
TypeCheckLimits limits,
|
||||||
|
@ -1212,6 +1294,7 @@ ModulePtr check(
|
||||||
moduleResolver,
|
moduleResolver,
|
||||||
fileResolver,
|
fileResolver,
|
||||||
parentScope,
|
parentScope,
|
||||||
|
typeFunctionScope,
|
||||||
std::move(prepareModuleScope),
|
std::move(prepareModuleScope),
|
||||||
options,
|
options,
|
||||||
limits,
|
limits,
|
||||||
|
@ -1222,7 +1305,7 @@ ModulePtr check(
|
||||||
|
|
||||||
struct InternalTypeFinder : TypeOnceVisitor
|
struct InternalTypeFinder : TypeOnceVisitor
|
||||||
{
|
{
|
||||||
bool visit(TypeId, const ClassType&) override
|
bool visit(TypeId, const ExternType&) override
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -1273,6 +1356,7 @@ ModulePtr check(
|
||||||
NotNull<ModuleResolver> moduleResolver,
|
NotNull<ModuleResolver> moduleResolver,
|
||||||
NotNull<FileResolver> fileResolver,
|
NotNull<FileResolver> fileResolver,
|
||||||
const ScopePtr& parentScope,
|
const ScopePtr& parentScope,
|
||||||
|
const ScopePtr& typeFunctionScope,
|
||||||
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope,
|
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope,
|
||||||
FrontendOptions options,
|
FrontendOptions options,
|
||||||
TypeCheckLimits limits,
|
TypeCheckLimits limits,
|
||||||
|
@ -1285,11 +1369,15 @@ ModulePtr check(
|
||||||
LUAU_TIMETRACE_ARGUMENT("name", sourceModule.humanReadableName.c_str());
|
LUAU_TIMETRACE_ARGUMENT("name", sourceModule.humanReadableName.c_str());
|
||||||
|
|
||||||
ModulePtr result = std::make_shared<Module>();
|
ModulePtr result = std::make_shared<Module>();
|
||||||
|
result->checkedInNewSolver = true;
|
||||||
result->name = sourceModule.name;
|
result->name = sourceModule.name;
|
||||||
result->humanReadableName = sourceModule.humanReadableName;
|
result->humanReadableName = sourceModule.humanReadableName;
|
||||||
result->mode = mode;
|
result->mode = mode;
|
||||||
result->internalTypes.owningModule = result.get();
|
result->internalTypes.owningModule = result.get();
|
||||||
result->interfaceTypes.owningModule = result.get();
|
result->interfaceTypes.owningModule = result.get();
|
||||||
|
result->allocator = sourceModule.allocator;
|
||||||
|
result->names = sourceModule.names;
|
||||||
|
result->root = sourceModule.root;
|
||||||
|
|
||||||
iceHandler->moduleName = sourceModule.name;
|
iceHandler->moduleName = sourceModule.name;
|
||||||
|
|
||||||
|
@ -1304,19 +1392,7 @@ ModulePtr check(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DataFlowGraph oldDfg = DataFlowGraphBuilder::build(sourceModule.root, iceHandler);
|
DataFlowGraph dfg = DataFlowGraphBuilder::build(sourceModule.root, NotNull{&result->defArena}, NotNull{&result->keyArena}, 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
UnifierSharedState unifierState{iceHandler};
|
UnifierSharedState unifierState{iceHandler};
|
||||||
unifierState.counters.recursionLimit = FInt::LuauTypeInferRecursionLimit;
|
unifierState.counters.recursionLimit = FInt::LuauTypeInferRecursionLimit;
|
||||||
|
@ -1326,8 +1402,7 @@ ModulePtr check(
|
||||||
SimplifierPtr simplifier = newSimplifier(NotNull{&result->internalTypes}, builtinTypes);
|
SimplifierPtr simplifier = newSimplifier(NotNull{&result->internalTypes}, builtinTypes);
|
||||||
TypeFunctionRuntime typeFunctionRuntime{iceHandler, NotNull{&limits}};
|
TypeFunctionRuntime typeFunctionRuntime{iceHandler, NotNull{&limits}};
|
||||||
|
|
||||||
if (FFlag::LuauUserDefinedTypeFunctionNoEvaluation)
|
typeFunctionRuntime.allowEvaluation = FFlag::LuauTypeFunResultInAutocomplete || sourceModule.parseErrors.empty();
|
||||||
typeFunctionRuntime.allowEvaluation = sourceModule.parseErrors.empty();
|
|
||||||
|
|
||||||
ConstraintGenerator cg{
|
ConstraintGenerator cg{
|
||||||
result,
|
result,
|
||||||
|
@ -1338,35 +1413,66 @@ ModulePtr check(
|
||||||
builtinTypes,
|
builtinTypes,
|
||||||
iceHandler,
|
iceHandler,
|
||||||
parentScope,
|
parentScope,
|
||||||
|
typeFunctionScope,
|
||||||
std::move(prepareModuleScope),
|
std::move(prepareModuleScope),
|
||||||
logger.get(),
|
logger.get(),
|
||||||
NotNull{dfgForConstraintGeneration},
|
NotNull{&dfg},
|
||||||
requireCycles
|
requireCycles
|
||||||
};
|
};
|
||||||
|
|
||||||
cg.visitModuleRoot(sourceModule.root);
|
// FIXME: Delete this flag when clipping FFlag::DebugLuauGreedyGeneralization.
|
||||||
result->errors = std::move(cg.errors);
|
//
|
||||||
|
// 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{
|
if (FFlag::DebugLuauGreedyGeneralization)
|
||||||
NotNull{&normalizer},
|
{
|
||||||
NotNull{simplifier.get()},
|
ConstraintSet constraintSet = cg.run(sourceModule.root);
|
||||||
NotNull{&typeFunctionRuntime},
|
result->errors = std::move(constraintSet.errors);
|
||||||
NotNull(cg.rootScope),
|
|
||||||
borrowConstraints(cg.constraints),
|
cs.emplace(
|
||||||
result->name,
|
NotNull{&normalizer},
|
||||||
moduleResolver,
|
NotNull{simplifier.get()},
|
||||||
requireCycles,
|
NotNull{&typeFunctionRuntime},
|
||||||
logger.get(),
|
result->name,
|
||||||
NotNull{dfgForConstraintGeneration},
|
moduleResolver,
|
||||||
limits
|
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)
|
if (options.randomizeConstraintResolutionSeed)
|
||||||
cs.randomize(*options.randomizeConstraintResolutionSeed);
|
cs->randomize(*options.randomizeConstraintResolutionSeed);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
cs.run();
|
cs->run();
|
||||||
}
|
}
|
||||||
catch (const TimeLimitError&)
|
catch (const TimeLimitError&)
|
||||||
{
|
{
|
||||||
|
@ -1386,12 +1492,12 @@ ModulePtr check(
|
||||||
printf("%s\n", output.c_str());
|
printf("%s\n", output.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
for (TypeError& e : cs.errors)
|
for (TypeError& e : cs->errors)
|
||||||
result->errors.emplace_back(std::move(e));
|
result->errors.emplace_back(std::move(e));
|
||||||
|
|
||||||
result->scopes = std::move(cg.scopes);
|
result->scopes = std::move(cg.scopes);
|
||||||
result->type = sourceModule.type;
|
result->type = sourceModule.type;
|
||||||
result->upperBoundContributors = std::move(cs.upperBoundContributors);
|
result->upperBoundContributors = std::move(cs->upperBoundContributors);
|
||||||
|
|
||||||
if (result->timeout || result->cancelled)
|
if (result->timeout || result->cancelled)
|
||||||
{
|
{
|
||||||
|
@ -1411,38 +1517,30 @@ ModulePtr check(
|
||||||
switch (mode)
|
switch (mode)
|
||||||
{
|
{
|
||||||
case Mode::Nonstrict:
|
case Mode::Nonstrict:
|
||||||
if (FFlag::LuauStoreDFGOnModule2)
|
Luau::checkNonStrict(
|
||||||
{
|
builtinTypes,
|
||||||
Luau::checkNonStrict(
|
NotNull{simplifier.get()},
|
||||||
builtinTypes,
|
NotNull{&typeFunctionRuntime},
|
||||||
NotNull{&typeFunctionRuntime},
|
iceHandler,
|
||||||
iceHandler,
|
NotNull{&unifierState},
|
||||||
NotNull{&unifierState},
|
NotNull{&dfg},
|
||||||
NotNull{dfgForConstraintGeneration},
|
NotNull{&limits},
|
||||||
NotNull{&limits},
|
sourceModule,
|
||||||
sourceModule,
|
result.get()
|
||||||
result.get()
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Luau::checkNonStrict(
|
|
||||||
builtinTypes,
|
|
||||||
NotNull{&typeFunctionRuntime},
|
|
||||||
iceHandler,
|
|
||||||
NotNull{&unifierState},
|
|
||||||
NotNull{&oldDfg},
|
|
||||||
NotNull{&limits},
|
|
||||||
sourceModule,
|
|
||||||
result.get()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case Mode::Definition:
|
case Mode::Definition:
|
||||||
// fallthrough intentional
|
// fallthrough intentional
|
||||||
case Mode::Strict:
|
case Mode::Strict:
|
||||||
Luau::check(
|
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;
|
break;
|
||||||
case Mode::NoCheck:
|
case Mode::NoCheck:
|
||||||
|
@ -1527,6 +1625,7 @@ ModulePtr Frontend::check(
|
||||||
NotNull{forAutocomplete ? &moduleResolverForAutocomplete : &moduleResolver},
|
NotNull{forAutocomplete ? &moduleResolverForAutocomplete : &moduleResolver},
|
||||||
NotNull{fileResolver},
|
NotNull{fileResolver},
|
||||||
environmentScope ? *environmentScope : globals.globalScope,
|
environmentScope ? *environmentScope : globals.globalScope,
|
||||||
|
globals.globalTypeFunctionScope,
|
||||||
prepareModuleScopeWrap,
|
prepareModuleScopeWrap,
|
||||||
options,
|
options,
|
||||||
typeCheckLimits,
|
typeCheckLimits,
|
||||||
|
@ -1624,6 +1723,14 @@ std::pair<SourceNode*, SourceModule*> Frontend::getSourceNode(const ModuleName&
|
||||||
|
|
||||||
sourceNode->name = sourceModule->name;
|
sourceNode->name = sourceModule->name;
|
||||||
sourceNode->humanReadableName = sourceModule->humanReadableName;
|
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->requireSet.clear();
|
||||||
sourceNode->requireLocations.clear();
|
sourceNode->requireLocations.clear();
|
||||||
sourceNode->dirtySourceModule = false;
|
sourceNode->dirtySourceModule = false;
|
||||||
|
@ -1745,11 +1852,13 @@ std::string FrontendModuleResolver::getHumanReadableModuleName(const ModuleName&
|
||||||
return frontend->fileResolver->getHumanReadableModuleName(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);
|
std::scoped_lock lock(moduleMutex);
|
||||||
|
|
||||||
|
bool replaced = modules.count(moduleName) > 0;
|
||||||
modules[moduleName] = std::move(module);
|
modules[moduleName] = std::move(module);
|
||||||
|
return replaced;
|
||||||
}
|
}
|
||||||
|
|
||||||
void FrontendModuleResolver::clearModules()
|
void FrontendModuleResolver::clearModules()
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -9,6 +9,7 @@ GlobalTypes::GlobalTypes(NotNull<BuiltinTypes> builtinTypes)
|
||||||
: builtinTypes(builtinTypes)
|
: builtinTypes(builtinTypes)
|
||||||
{
|
{
|
||||||
globalScope = std::make_shared<Scope>(globalTypes.addTypePack(TypePackVar{FreeTypePack{TypeLevel{}}}));
|
globalScope = std::make_shared<Scope>(globalTypes.addTypePack(TypePackVar{FreeTypePack{TypeLevel{}}}));
|
||||||
|
globalTypeFunctionScope = std::make_shared<Scope>(globalTypes.addTypePack(TypePackVar{FreeTypePack{TypeLevel{}}}));
|
||||||
|
|
||||||
globalScope->addBuiltinTypeBinding("any", TypeFun{{}, builtinTypes->anyType});
|
globalScope->addBuiltinTypeBinding("any", TypeFun{{}, builtinTypes->anyType});
|
||||||
globalScope->addBuiltinTypeBinding("nil", TypeFun{{}, builtinTypes->nilType});
|
globalScope->addBuiltinTypeBinding("nil", TypeFun{{}, builtinTypes->nilType});
|
||||||
|
|
169
Analysis/src/InferPolarity.cpp
Normal file
169
Analysis/src/InferPolarity.cpp
Normal 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
|
|
@ -49,7 +49,7 @@ bool Instantiation::ignoreChildren(TypeId ty)
|
||||||
{
|
{
|
||||||
if (log->getMutable<FunctionType>(ty))
|
if (log->getMutable<FunctionType>(ty))
|
||||||
return true;
|
return true;
|
||||||
else if (get<ClassType>(ty))
|
else if (get<ExternType>(ty))
|
||||||
return true;
|
return true;
|
||||||
else
|
else
|
||||||
return false;
|
return false;
|
||||||
|
@ -60,10 +60,8 @@ TypeId Instantiation::clean(TypeId ty)
|
||||||
const FunctionType* ftv = log->getMutable<FunctionType>(ty);
|
const FunctionType* ftv = log->getMutable<FunctionType>(ty);
|
||||||
LUAU_ASSERT(ftv);
|
LUAU_ASSERT(ftv);
|
||||||
|
|
||||||
FunctionType clone = FunctionType{level, scope, ftv->argTypes, ftv->retTypes, ftv->definition, ftv->hasSelf};
|
FunctionType clone = FunctionType{level, ftv->argTypes, ftv->retTypes, ftv->definition, ftv->hasSelf};
|
||||||
clone.magicFunction = ftv->magicFunction;
|
clone.magic = ftv->magic;
|
||||||
clone.dcrMagicFunction = ftv->dcrMagicFunction;
|
|
||||||
clone.dcrMagicRefinement = ftv->dcrMagicRefinement;
|
|
||||||
clone.tags = ftv->tags;
|
clone.tags = ftv->tags;
|
||||||
clone.argNames = ftv->argNames;
|
clone.argNames = ftv->argNames;
|
||||||
TypeId result = addType(std::move(clone));
|
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.
|
// whenever we quantify, so the vectors overlap if and only if they are equal.
|
||||||
return (!generics.empty() || !genericPacks.empty()) && (ftv->generics == generics) && (ftv->genericPacks == genericPacks);
|
return (!generics.empty() || !genericPacks.empty()) && (ftv->generics == generics) && (ftv->genericPacks == genericPacks);
|
||||||
}
|
}
|
||||||
else if (get<ClassType>(ty))
|
else if (get<ExternType>(ty))
|
||||||
return true;
|
return true;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -165,7 +163,7 @@ TypeId ReplaceGenerics::clean(TypeId ty)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return addType(FreeType{scope, level});
|
return arena->freshType(builtinTypes, scope, level);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ namespace Luau
|
||||||
|
|
||||||
bool Instantiation2::ignoreChildren(TypeId ty)
|
bool Instantiation2::ignoreChildren(TypeId ty)
|
||||||
{
|
{
|
||||||
if (get<ClassType>(ty))
|
if (get<ExternType>(ty))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (auto ftv = get<FunctionType>(ty))
|
if (auto ftv = get<FunctionType>(ty))
|
||||||
|
|
|
@ -193,8 +193,8 @@ static void errorToString(std::ostream& stream, const T& err)
|
||||||
stream << "NormalizationTooComplex { }";
|
stream << "NormalizationTooComplex { }";
|
||||||
else if constexpr (std::is_same_v<T, TypePackMismatch>)
|
else if constexpr (std::is_same_v<T, TypePackMismatch>)
|
||||||
stream << "TypePackMismatch { wanted = '" + toString(err.wantedTp) + "', given = '" + toString(err.givenTp) + "' }";
|
stream << "TypePackMismatch { wanted = '" + toString(err.wantedTp) + "', given = '" + toString(err.givenTp) + "' }";
|
||||||
else if constexpr (std::is_same_v<T, DynamicPropertyLookupOnClassesUnsafe>)
|
else if constexpr (std::is_same_v<T, DynamicPropertyLookupOnExternTypesUnsafe>)
|
||||||
stream << "DynamicPropertyLookupOnClassesUnsafe { " << toString(err.ty) << " }";
|
stream << "DynamicPropertyLookupOnExternTypesUnsafe { " << toString(err.ty) << " }";
|
||||||
else if constexpr (std::is_same_v<T, UninhabitedTypeFunction>)
|
else if constexpr (std::is_same_v<T, UninhabitedTypeFunction>)
|
||||||
stream << "UninhabitedTypeFunction { " << toString(err.ty) << " }";
|
stream << "UninhabitedTypeFunction { " << toString(err.ty) << " }";
|
||||||
else if constexpr (std::is_same_v<T, ExplicitFunctionAnnotationRecommended>)
|
else if constexpr (std::is_same_v<T, ExplicitFunctionAnnotationRecommended>)
|
||||||
|
@ -229,6 +229,8 @@ static void errorToString(std::ostream& stream, const T& err)
|
||||||
stream << "UnexpectedTypePackInSubtyping { tp = '" + toString(err.tp) + "' }";
|
stream << "UnexpectedTypePackInSubtyping { tp = '" + toString(err.tp) + "' }";
|
||||||
else if constexpr (std::is_same_v<T, UserDefinedTypeFunctionError>)
|
else if constexpr (std::is_same_v<T, UserDefinedTypeFunctionError>)
|
||||||
stream << "UserDefinedTypeFunctionError { " << err.message << " }";
|
stream << "UserDefinedTypeFunctionError { " << err.message << " }";
|
||||||
|
else if constexpr (std::is_same_v<T, ReservedIdentifier>)
|
||||||
|
stream << "ReservedIdentifier { " << err.name << " }";
|
||||||
else if constexpr (std::is_same_v<T, CannotAssignToNever>)
|
else if constexpr (std::is_same_v<T, CannotAssignToNever>)
|
||||||
{
|
{
|
||||||
stream << "CannotAssignToNever { rvalueType = '" << toString(err.rhsType) << "', reason = '" << err.reason << "', cause = { ";
|
stream << "CannotAssignToNever { rvalueType = '" << toString(err.rhsType) << "', reason = '" << err.reason << "', cause = { ";
|
||||||
|
|
|
@ -17,9 +17,11 @@ LUAU_FASTINTVARIABLE(LuauSuggestionDistance, 4)
|
||||||
LUAU_FASTFLAG(LuauSolverV2)
|
LUAU_FASTFLAG(LuauSolverV2)
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauAttribute)
|
LUAU_FASTFLAG(LuauAttribute)
|
||||||
LUAU_FASTFLAG(LuauNativeAttribute)
|
|
||||||
LUAU_FASTFLAGVARIABLE(LintRedundantNativeAttribute)
|
LUAU_FASTFLAGVARIABLE(LintRedundantNativeAttribute)
|
||||||
|
|
||||||
|
LUAU_FASTFLAG(LuauDeprecatedAttribute)
|
||||||
|
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
|
||||||
|
@ -907,6 +909,11 @@ private:
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool visit(AstTypePack* node) override
|
||||||
|
{
|
||||||
|
return FFlag::LuauStoreReturnTypesAsPackOnAst;
|
||||||
|
}
|
||||||
|
|
||||||
bool visit(AstTypeReference* node) override
|
bool visit(AstTypeReference* node) override
|
||||||
{
|
{
|
||||||
if (!node->prefix)
|
if (!node->prefix)
|
||||||
|
@ -1969,6 +1976,11 @@ private:
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool visit(AstTypePack* node) override
|
||||||
|
{
|
||||||
|
return FFlag::LuauStoreReturnTypesAsPackOnAst;
|
||||||
|
}
|
||||||
|
|
||||||
bool visit(AstTypeTable* node) override
|
bool visit(AstTypeTable* node) override
|
||||||
{
|
{
|
||||||
if (FFlag::LuauSolverV2)
|
if (FFlag::LuauSolverV2)
|
||||||
|
@ -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
|
bool visit(AstExprIndexName* node) override
|
||||||
{
|
{
|
||||||
if (std::optional<TypeId> ty = context->getType(node->expr))
|
if (std::optional<TypeId> ty = context->getType(node->expr))
|
||||||
|
@ -2320,24 +2383,65 @@ private:
|
||||||
|
|
||||||
void check(AstExprIndexName* node, TypeId ty)
|
void check(AstExprIndexName* node, TypeId ty)
|
||||||
{
|
{
|
||||||
if (const ClassType* cty = get<ClassType>(ty))
|
if (const ExternType* cty = get<ExternType>(ty))
|
||||||
{
|
{
|
||||||
const Property* prop = lookupClassProp(cty, node->index.value);
|
const Property* prop = lookupExternTypeProp(cty, node->index.value);
|
||||||
|
|
||||||
if (prop && prop->deprecated)
|
if (prop && prop->deprecated)
|
||||||
report(node->location, *prop, cty->name.c_str(), node->index.value);
|
report(node->location, *prop, cty->name.c_str(), node->index.value);
|
||||||
|
else if (FFlag::LuauDeprecatedAttribute && prop)
|
||||||
|
{
|
||||||
|
if (std::optional<TypeId> ty = prop->readTy)
|
||||||
|
{
|
||||||
|
const FunctionType* fty = get<FunctionType>(follow(ty));
|
||||||
|
bool shouldReport = fty && fty->isDeprecatedFunction && !inScope(fty);
|
||||||
|
|
||||||
|
if (shouldReport)
|
||||||
|
{
|
||||||
|
const char* className = nullptr;
|
||||||
|
if (AstExprGlobal* global = node->expr->as<AstExprGlobal>())
|
||||||
|
className = global->name.value;
|
||||||
|
|
||||||
|
const char* functionName = node->index.value;
|
||||||
|
|
||||||
|
report(node->location, className, functionName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (const TableType* tty = get<TableType>(ty))
|
else if (const TableType* tty = get<TableType>(ty))
|
||||||
{
|
{
|
||||||
auto prop = tty->props.find(node->index.value);
|
auto prop = tty->props.find(node->index.value);
|
||||||
|
|
||||||
if (prop != tty->props.end() && prop->second.deprecated)
|
if (prop != tty->props.end())
|
||||||
{
|
{
|
||||||
// strip synthetic typeof() for builtin tables
|
if (prop->second.deprecated)
|
||||||
if (tty->name && tty->name->compare(0, 7, "typeof(") == 0 && tty->name->back() == ')')
|
{
|
||||||
report(node->location, prop->second, tty->name->substr(7, tty->name->length() - 8).c_str(), node->index.value);
|
// strip synthetic typeof() for builtin tables
|
||||||
else
|
if (tty->name && tty->name->compare(0, 7, "typeof(") == 0 && tty->name->back() == ')')
|
||||||
report(node->location, prop->second, tty->name ? tty->name->c_str() : nullptr, node->index.value);
|
report(node->location, prop->second, tty->name->substr(7, tty->name->length() - 8).c_str(), node->index.value);
|
||||||
|
else
|
||||||
|
report(node->location, prop->second, tty->name ? tty->name->c_str() : nullptr, node->index.value);
|
||||||
|
}
|
||||||
|
else if (FFlag::LuauDeprecatedAttribute)
|
||||||
|
{
|
||||||
|
if (std::optional<TypeId> ty = prop->second.readTy)
|
||||||
|
{
|
||||||
|
const FunctionType* fty = get<FunctionType>(follow(ty));
|
||||||
|
bool shouldReport = fty && fty->isDeprecatedFunction && !inScope(fty);
|
||||||
|
|
||||||
|
if (shouldReport)
|
||||||
|
{
|
||||||
|
const char* className = nullptr;
|
||||||
|
if (AstExprGlobal* global = node->expr->as<AstExprGlobal>())
|
||||||
|
className = global->name.value;
|
||||||
|
|
||||||
|
const char* functionName = node->index.value;
|
||||||
|
|
||||||
|
report(node->location, className, functionName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
void report(const Location& location, const Property& prop, const char* container, const char* field)
|
||||||
{
|
{
|
||||||
std::string suggestion = prop.deprecatedSuggestion.empty() ? "" : format(", use '%s' instead", prop.deprecatedSuggestion.c_str());
|
std::string suggestion = prop.deprecatedSuggestion.empty() ? "" : format(", use '%s' instead", prop.deprecatedSuggestion.c_str());
|
||||||
|
@ -2365,6 +2489,63 @@ private:
|
||||||
else
|
else
|
||||||
emitWarning(*context, LintWarning::Code_DeprecatedApi, location, "Member '%s' is deprecated%s", field, suggestion.c_str());
|
emitWarning(*context, LintWarning::Code_DeprecatedApi, location, "Member '%s' is deprecated%s", field, suggestion.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void report(const Location& location, const char* tableName, const char* functionName)
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
|
||||||
|
|
||||||
|
if (tableName)
|
||||||
|
emitWarning(*context, LintWarning::Code_DeprecatedApi, location, "Member '%s.%s' is deprecated", tableName, functionName);
|
||||||
|
else
|
||||||
|
emitWarning(*context, LintWarning::Code_DeprecatedApi, location, "Member '%s' is deprecated", functionName);
|
||||||
|
}
|
||||||
|
|
||||||
|
void report(const Location& location, const char* functionName)
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
|
||||||
|
|
||||||
|
emitWarning(*context, LintWarning::Code_DeprecatedApi, location, "Function '%s' is deprecated", functionName);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<const FunctionType*> functionTypeScopeStack;
|
||||||
|
|
||||||
|
void pushScope(const FunctionType* fty)
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
|
||||||
|
LUAU_ASSERT(fty);
|
||||||
|
|
||||||
|
functionTypeScopeStack.push_back(fty);
|
||||||
|
}
|
||||||
|
|
||||||
|
void popScope(const FunctionType* fty)
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
|
||||||
|
LUAU_ASSERT(fty);
|
||||||
|
|
||||||
|
LUAU_ASSERT(fty == functionTypeScopeStack.back());
|
||||||
|
functionTypeScopeStack.pop_back();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool inScope(const FunctionType* fty) const
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
|
||||||
|
LUAU_ASSERT(fty);
|
||||||
|
|
||||||
|
return std::find(functionTypeScopeStack.begin(), functionTypeScopeStack.end(), fty) != functionTypeScopeStack.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
const FunctionType* getFunctionType(AstExpr* node)
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
|
||||||
|
|
||||||
|
std::optional<TypeId> ty = context->getType(node);
|
||||||
|
if (!ty)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
const FunctionType* fty = get<FunctionType>(follow(ty));
|
||||||
|
|
||||||
|
return fty;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class LintTableOperations : AstVisitor
|
class LintTableOperations : AstVisitor
|
||||||
|
@ -3239,7 +3420,6 @@ static void lintComments(LintContext& context, const std::vector<HotComment>& ho
|
||||||
|
|
||||||
static bool hasNativeCommentDirective(const std::vector<HotComment>& hotcomments)
|
static bool hasNativeCommentDirective(const std::vector<HotComment>& hotcomments)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(FFlag::LuauNativeAttribute);
|
|
||||||
LUAU_ASSERT(FFlag::LintRedundantNativeAttribute);
|
LUAU_ASSERT(FFlag::LintRedundantNativeAttribute);
|
||||||
|
|
||||||
for (const HotComment& hc : hotcomments)
|
for (const HotComment& hc : hotcomments)
|
||||||
|
@ -3265,7 +3445,6 @@ struct LintRedundantNativeAttribute : AstVisitor
|
||||||
public:
|
public:
|
||||||
LUAU_NOINLINE static void process(LintContext& context)
|
LUAU_NOINLINE static void process(LintContext& context)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(FFlag::LuauNativeAttribute);
|
|
||||||
LUAU_ASSERT(FFlag::LintRedundantNativeAttribute);
|
LUAU_ASSERT(FFlag::LintRedundantNativeAttribute);
|
||||||
|
|
||||||
LintRedundantNativeAttribute pass;
|
LintRedundantNativeAttribute pass;
|
||||||
|
@ -3389,7 +3568,7 @@ std::vector<LintWarning> lint(
|
||||||
if (context.warningEnabled(LintWarning::Code_ComparisonPrecedence))
|
if (context.warningEnabled(LintWarning::Code_ComparisonPrecedence))
|
||||||
LintComparisonPrecedence::process(context);
|
LintComparisonPrecedence::process(context);
|
||||||
|
|
||||||
if (FFlag::LuauNativeAttribute && FFlag::LintRedundantNativeAttribute && context.warningEnabled(LintWarning::Code_RedundantNativeAttribute))
|
if (FFlag::LintRedundantNativeAttribute && context.warningEnabled(LintWarning::Code_RedundantNativeAttribute))
|
||||||
{
|
{
|
||||||
if (hasNativeCommentDirective(hotcomments))
|
if (hasNativeCommentDirective(hotcomments))
|
||||||
LintRedundantNativeAttribute::process(context);
|
LintRedundantNativeAttribute::process(context);
|
||||||
|
|
|
@ -15,11 +15,29 @@
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauSolverV2);
|
LUAU_FASTFLAG(LuauSolverV2);
|
||||||
LUAU_DYNAMIC_FASTINT(LuauTypeSolverRelease)
|
LUAU_FASTFLAG(LuauRetainDefinitionAliasLocations)
|
||||||
|
|
||||||
namespace Luau
|
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)
|
static bool contains(Position pos, Comment comment)
|
||||||
{
|
{
|
||||||
if (comment.location.contains(pos))
|
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
|
else if (comment.type == Lexeme::BrokenComment && comment.location.begin <= pos) // Broken comments are broken specifically because they don't
|
||||||
// have an end
|
// have an end
|
||||||
return true;
|
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;
|
return true;
|
||||||
else
|
else
|
||||||
return false;
|
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(
|
auto iter = std::lower_bound(
|
||||||
commentLocations.begin(),
|
commentLocations.begin(),
|
||||||
|
@ -41,6 +61,8 @@ static bool isWithinComment(const std::vector<Comment>& commentLocations, Positi
|
||||||
Comment{Lexeme::Comment, Location{pos, pos}},
|
Comment{Lexeme::Comment, Location{pos, pos}},
|
||||||
[](const Comment& a, const Comment& b)
|
[](const Comment& a, const Comment& b)
|
||||||
{
|
{
|
||||||
|
if (a.type == Lexeme::Comment)
|
||||||
|
return a.location.end.line < b.location.end.line;
|
||||||
return a.location.end < b.location.end;
|
return a.location.end < b.location.end;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -132,34 +154,25 @@ struct ClonePublicInterface : Substitution
|
||||||
}
|
}
|
||||||
|
|
||||||
ftv->level = TypeLevel{0, 0};
|
ftv->level = TypeLevel{0, 0};
|
||||||
if (FFlag::LuauSolverV2 && DFInt::LuauTypeSolverRelease >= 645)
|
|
||||||
ftv->scope = nullptr;
|
|
||||||
}
|
}
|
||||||
else if (TableType* ttv = getMutable<TableType>(result))
|
else if (TableType* ttv = getMutable<TableType>(result))
|
||||||
{
|
{
|
||||||
ttv->level = TypeLevel{0, 0};
|
ttv->level = TypeLevel{0, 0};
|
||||||
if (FFlag::LuauSolverV2 && DFInt::LuauTypeSolverRelease >= 645)
|
if (FFlag::LuauSolverV2)
|
||||||
ttv->scope = nullptr;
|
ttv->scope = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FFlag::LuauSolverV2 && DFInt::LuauTypeSolverRelease >= 645)
|
if (FFlag::LuauSolverV2)
|
||||||
{
|
{
|
||||||
if (auto freety = getMutable<FreeType>(result))
|
if (auto freety = getMutable<FreeType>(result))
|
||||||
{
|
{
|
||||||
if (DFInt::LuauTypeSolverRelease >= 646)
|
module->errors.emplace_back(
|
||||||
{
|
freety->scope->location,
|
||||||
module->errors.emplace_back(
|
module->name,
|
||||||
freety->scope->location,
|
InternalError{"Free type is escaping its module; please report this bug at "
|
||||||
module->name,
|
"https://github.com/luau-lang/luau/issues"}
|
||||||
InternalError{"Free type is escaping its module; please report this bug at "
|
);
|
||||||
"https://github.com/luau-lang/luau/issues"}
|
result = builtinTypes->errorRecoveryType();
|
||||||
);
|
|
||||||
result = builtinTypes->errorRecoveryType();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
freety->scope = nullptr;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (auto genericty = getMutable<GenericType>(result))
|
else if (auto genericty = getMutable<GenericType>(result))
|
||||||
{
|
{
|
||||||
|
@ -172,26 +185,18 @@ struct ClonePublicInterface : Substitution
|
||||||
|
|
||||||
TypePackId clean(TypePackId tp) override
|
TypePackId clean(TypePackId tp) override
|
||||||
{
|
{
|
||||||
if (FFlag::LuauSolverV2 && DFInt::LuauTypeSolverRelease >= 645)
|
if (FFlag::LuauSolverV2)
|
||||||
{
|
{
|
||||||
auto clonedTp = clone(tp);
|
auto clonedTp = clone(tp);
|
||||||
if (auto ftp = getMutable<FreeTypePack>(clonedTp))
|
if (auto ftp = getMutable<FreeTypePack>(clonedTp))
|
||||||
{
|
{
|
||||||
|
module->errors.emplace_back(
|
||||||
if (DFInt::LuauTypeSolverRelease >= 646)
|
ftp->scope->location,
|
||||||
{
|
module->name,
|
||||||
module->errors.emplace_back(
|
InternalError{"Free type pack is escaping its module; please report this bug at "
|
||||||
ftp->scope->location,
|
"https://github.com/luau-lang/luau/issues"}
|
||||||
module->name,
|
);
|
||||||
InternalError{"Free type pack is escaping its module; please report this bug at "
|
clonedTp = builtinTypes->errorRecoveryTypePack();
|
||||||
"https://github.com/luau-lang/luau/issues"}
|
|
||||||
);
|
|
||||||
clonedTp = builtinTypes->errorRecoveryTypePack();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ftp->scope = nullptr;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (auto gtp = getMutable<GenericTypePack>(clonedTp))
|
else if (auto gtp = getMutable<GenericTypePack>(clonedTp))
|
||||||
gtp->scope = nullptr;
|
gtp->scope = nullptr;
|
||||||
|
@ -260,7 +265,10 @@ struct ClonePublicInterface : Substitution
|
||||||
|
|
||||||
TypeId type = cloneType(tf.type);
|
TypeId type = cloneType(tf.type);
|
||||||
|
|
||||||
return TypeFun{typeParams, typePackParams, type};
|
if (FFlag::LuauRetainDefinitionAliasLocations)
|
||||||
|
return TypeFun{typeParams, typePackParams, type, tf.definitionLocation};
|
||||||
|
else
|
||||||
|
return TypeFun{typeParams, typePackParams, type};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -17,12 +17,12 @@
|
||||||
|
|
||||||
LUAU_FASTFLAGVARIABLE(DebugLuauCheckNormalizeInvariant)
|
LUAU_FASTFLAGVARIABLE(DebugLuauCheckNormalizeInvariant)
|
||||||
|
|
||||||
LUAU_FASTINTVARIABLE(LuauNormalizeCacheLimit, 100000);
|
LUAU_FASTINTVARIABLE(LuauNormalizeCacheLimit, 100000)
|
||||||
LUAU_FASTFLAG(LuauSolverV2);
|
|
||||||
|
|
||||||
LUAU_FASTINTVARIABLE(LuauNormalizeIntersectionLimit, 200)
|
LUAU_FASTINTVARIABLE(LuauNormalizeIntersectionLimit, 200)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauNormalizationTracksCyclicPairsThroughInhabitance);
|
LUAU_FASTINTVARIABLE(LuauNormalizeUnionLimit, 100)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauIntersectNormalsNeedsToTrackResourceLimits);
|
LUAU_FASTFLAG(LuauSolverV2)
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauFixInfiniteRecursionInNormalization)
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauNormalizationCatchMetatableCycles)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
@ -249,23 +249,23 @@ bool isSubtype(const NormalizedStringType& subStr, const NormalizedStringType& s
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NormalizedClassType::pushPair(TypeId ty, TypeIds negations)
|
void NormalizedExternType::pushPair(TypeId ty, TypeIds negations)
|
||||||
{
|
{
|
||||||
auto result = classes.insert(std::make_pair(ty, std::move(negations)));
|
auto result = externTypes.insert(std::make_pair(ty, std::move(negations)));
|
||||||
if (result.second)
|
if (result.second)
|
||||||
ordering.push_back(ty);
|
ordering.push_back(ty);
|
||||||
LUAU_ASSERT(ordering.size() == classes.size());
|
LUAU_ASSERT(ordering.size() == externTypes.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
void NormalizedClassType::resetToNever()
|
void NormalizedExternType::resetToNever()
|
||||||
{
|
{
|
||||||
ordering.clear();
|
ordering.clear();
|
||||||
classes.clear();
|
externTypes.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NormalizedClassType::isNever() const
|
bool NormalizedExternType::isNever() const
|
||||||
{
|
{
|
||||||
return classes.empty();
|
return externTypes.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
void NormalizedFunctionType::resetToTop()
|
void NormalizedFunctionType::resetToTop()
|
||||||
|
@ -304,17 +304,17 @@ bool NormalizedType::isUnknown() const
|
||||||
|
|
||||||
// Otherwise, we can still be unknown!
|
// Otherwise, we can still be unknown!
|
||||||
bool hasAllPrimitives = isPrim(booleans, PrimitiveType::Boolean) && isPrim(nils, PrimitiveType::NilType) && isNumber(numbers) &&
|
bool hasAllPrimitives = isPrim(booleans, PrimitiveType::Boolean) && isPrim(nils, PrimitiveType::NilType) && isNumber(numbers) &&
|
||||||
strings.isString() && isPrim(threads, PrimitiveType::Thread) && isThread(threads);
|
strings.isString() && isThread(threads) && isBuffer(buffers);
|
||||||
|
|
||||||
// Check is class
|
// Check is class
|
||||||
bool isTopClass = false;
|
bool isTopExternType = false;
|
||||||
for (auto [t, disj] : classes.classes)
|
for (const auto& [t, disj] : externTypes.externTypes)
|
||||||
{
|
{
|
||||||
if (auto ct = get<ClassType>(t))
|
if (auto ct = get<ExternType>(t))
|
||||||
{
|
{
|
||||||
if (ct->name == "class" && disj.empty())
|
if (ct->name == "class" && disj.empty())
|
||||||
{
|
{
|
||||||
isTopClass = true;
|
isTopExternType = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -330,24 +330,24 @@ bool NormalizedType::isUnknown() const
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// any = unknown or error ==> we need to make sure we have all the unknown components, but not errors
|
// any = unknown or error ==> we need to make sure we have all the unknown components, but not errors
|
||||||
return get<NeverType>(errors) && hasAllPrimitives && isTopClass && isTopTable && functions.isTop;
|
return get<NeverType>(errors) && hasAllPrimitives && isTopExternType && isTopTable && functions.isTop;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NormalizedType::isExactlyNumber() const
|
bool NormalizedType::isExactlyNumber() const
|
||||||
{
|
{
|
||||||
return hasNumbers() && !hasTops() && !hasBooleans() && !hasClasses() && !hasErrors() && !hasNils() && !hasStrings() && !hasThreads() &&
|
return hasNumbers() && !hasTops() && !hasBooleans() && !hasExternTypes() && !hasErrors() && !hasNils() && !hasStrings() && !hasThreads() &&
|
||||||
!hasBuffers() && !hasTables() && !hasFunctions() && !hasTyvars();
|
!hasBuffers() && !hasTables() && !hasFunctions() && !hasTyvars();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NormalizedType::isSubtypeOfString() const
|
bool NormalizedType::isSubtypeOfString() const
|
||||||
{
|
{
|
||||||
return hasStrings() && !hasTops() && !hasBooleans() && !hasClasses() && !hasErrors() && !hasNils() && !hasNumbers() && !hasThreads() &&
|
return hasStrings() && !hasTops() && !hasBooleans() && !hasExternTypes() && !hasErrors() && !hasNils() && !hasNumbers() && !hasThreads() &&
|
||||||
!hasBuffers() && !hasTables() && !hasFunctions() && !hasTyvars();
|
!hasBuffers() && !hasTables() && !hasFunctions() && !hasTyvars();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NormalizedType::isSubtypeOfBooleans() const
|
bool NormalizedType::isSubtypeOfBooleans() const
|
||||||
{
|
{
|
||||||
return hasBooleans() && !hasTops() && !hasClasses() && !hasErrors() && !hasNils() && !hasNumbers() && !hasStrings() && !hasThreads() &&
|
return hasBooleans() && !hasTops() && !hasExternTypes() && !hasErrors() && !hasNils() && !hasNumbers() && !hasStrings() && !hasThreads() &&
|
||||||
!hasBuffers() && !hasTables() && !hasFunctions() && !hasTyvars();
|
!hasBuffers() && !hasTables() && !hasFunctions() && !hasTyvars();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -380,9 +380,9 @@ bool NormalizedType::hasBooleans() const
|
||||||
return !get<NeverType>(booleans);
|
return !get<NeverType>(booleans);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NormalizedType::hasClasses() const
|
bool NormalizedType::hasExternTypes() const
|
||||||
{
|
{
|
||||||
return !classes.isNever();
|
return !externTypes.isNever();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NormalizedType::hasErrors() const
|
bool NormalizedType::hasErrors() const
|
||||||
|
@ -440,7 +440,7 @@ bool NormalizedType::isFalsy() const
|
||||||
hasAFalse = !bs->value;
|
hasAFalse = !bs->value;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (hasAFalse || hasNils()) && (!hasTops() && !hasClasses() && !hasErrors() && !hasNumbers() && !hasStrings() && !hasThreads() &&
|
return (hasAFalse || hasNils()) && (!hasTops() && !hasExternTypes() && !hasErrors() && !hasNumbers() && !hasStrings() && !hasThreads() &&
|
||||||
!hasBuffers() && !hasTables() && !hasFunctions() && !hasTyvars());
|
!hasBuffers() && !hasTables() && !hasFunctions() && !hasTyvars());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -452,7 +452,7 @@ bool NormalizedType::isTruthy() const
|
||||||
static bool isShallowInhabited(const NormalizedType& norm)
|
static bool isShallowInhabited(const NormalizedType& norm)
|
||||||
{
|
{
|
||||||
// This test is just a shallow check, for example it returns `true` for `{ p : never }`
|
// This test is just a shallow check, for example it returns `true` for `{ p : never }`
|
||||||
return !get<NeverType>(norm.tops) || !get<NeverType>(norm.booleans) || !norm.classes.isNever() || !get<NeverType>(norm.errors) ||
|
return !get<NeverType>(norm.tops) || !get<NeverType>(norm.booleans) || !norm.externTypes.isNever() || !get<NeverType>(norm.errors) ||
|
||||||
!get<NeverType>(norm.nils) || !get<NeverType>(norm.numbers) || !norm.strings.isNever() || !get<NeverType>(norm.threads) ||
|
!get<NeverType>(norm.nils) || !get<NeverType>(norm.numbers) || !norm.strings.isNever() || !get<NeverType>(norm.threads) ||
|
||||||
!get<NeverType>(norm.buffers) || !norm.functions.isNever() || !norm.tables.empty() || !norm.tyvars.empty();
|
!get<NeverType>(norm.buffers) || !norm.functions.isNever() || !norm.tables.empty() || !norm.tyvars.empty();
|
||||||
}
|
}
|
||||||
|
@ -471,7 +471,7 @@ NormalizationResult Normalizer::isInhabited(const NormalizedType* norm, Set<Type
|
||||||
return NormalizationResult::HitLimits;
|
return NormalizationResult::HitLimits;
|
||||||
|
|
||||||
if (!get<NeverType>(norm->tops) || !get<NeverType>(norm->booleans) || !get<NeverType>(norm->errors) || !get<NeverType>(norm->nils) ||
|
if (!get<NeverType>(norm->tops) || !get<NeverType>(norm->booleans) || !get<NeverType>(norm->errors) || !get<NeverType>(norm->nils) ||
|
||||||
!get<NeverType>(norm->numbers) || !get<NeverType>(norm->threads) || !get<NeverType>(norm->buffers) || !norm->classes.isNever() ||
|
!get<NeverType>(norm->numbers) || !get<NeverType>(norm->threads) || !get<NeverType>(norm->buffers) || !norm->externTypes.isNever() ||
|
||||||
!norm->strings.isNever() || !norm->functions.isNever())
|
!norm->strings.isNever() || !norm->functions.isNever())
|
||||||
return NormalizationResult::True;
|
return NormalizationResult::True;
|
||||||
|
|
||||||
|
@ -579,7 +579,7 @@ NormalizationResult Normalizer::isIntersectionInhabited(TypeId left, TypeId righ
|
||||||
{
|
{
|
||||||
left = follow(left);
|
left = follow(left);
|
||||||
right = follow(right);
|
right = follow(right);
|
||||||
// We're asking if intersection is inahbited between left and right but we've already seen them ....
|
// We're asking if intersection is inhabited between left and right but we've already seen them ....
|
||||||
|
|
||||||
if (cacheInhabitance)
|
if (cacheInhabitance)
|
||||||
{
|
{
|
||||||
|
@ -619,13 +619,13 @@ static int tyvarIndex(TypeId ty)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool isTop(NotNull<BuiltinTypes> builtinTypes, const NormalizedClassType& classes)
|
static bool isTop(NotNull<BuiltinTypes> builtinTypes, const NormalizedExternType& externTypes)
|
||||||
{
|
{
|
||||||
if (classes.classes.size() != 1)
|
if (externTypes.externTypes.size() != 1)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
auto first = classes.classes.begin();
|
auto first = externTypes.externTypes.begin();
|
||||||
if (first->first != builtinTypes->classType)
|
if (first->first != builtinTypes->externType)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (!first->second.empty())
|
if (!first->second.empty())
|
||||||
|
@ -634,11 +634,11 @@ static bool isTop(NotNull<BuiltinTypes> builtinTypes, const NormalizedClassType&
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void resetToTop(NotNull<BuiltinTypes> builtinTypes, NormalizedClassType& classes)
|
static void resetToTop(NotNull<BuiltinTypes> builtinTypes, NormalizedExternType& externTypes)
|
||||||
{
|
{
|
||||||
classes.ordering.clear();
|
externTypes.ordering.clear();
|
||||||
classes.classes.clear();
|
externTypes.externTypes.clear();
|
||||||
classes.pushPair(builtinTypes->classType, TypeIds{});
|
externTypes.pushPair(builtinTypes->externType, TypeIds{});
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef LUAU_ASSERTENABLED
|
#ifdef LUAU_ASSERTENABLED
|
||||||
|
@ -762,50 +762,50 @@ static bool areNormalizedTables(const TypeIds& tys)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool areNormalizedClasses(const NormalizedClassType& tys)
|
static bool areNormalizedExternTypes(const NormalizedExternType& tys)
|
||||||
{
|
{
|
||||||
for (const auto& [ty, negations] : tys.classes)
|
for (const auto& [ty, negations] : tys.externTypes)
|
||||||
{
|
{
|
||||||
const ClassType* ctv = get<ClassType>(ty);
|
const ExternType* etv = get<ExternType>(ty);
|
||||||
if (!ctv)
|
if (!etv)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (TypeId negation : negations)
|
for (TypeId negation : negations)
|
||||||
{
|
{
|
||||||
const ClassType* nctv = get<ClassType>(negation);
|
const ExternType* nctv = get<ExternType>(negation);
|
||||||
if (!nctv)
|
if (!nctv)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isSubclass(nctv, ctv))
|
if (!isSubclass(nctv, etv))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto& [otherTy, otherNegations] : tys.classes)
|
for (const auto& [otherTy, otherNegations] : tys.externTypes)
|
||||||
{
|
{
|
||||||
if (otherTy == ty)
|
if (otherTy == ty)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
const ClassType* octv = get<ClassType>(otherTy);
|
const ExternType* octv = get<ExternType>(otherTy);
|
||||||
if (!octv)
|
if (!octv)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isSubclass(ctv, octv))
|
if (isSubclass(etv, octv))
|
||||||
{
|
{
|
||||||
auto iss = [ctv](TypeId t)
|
auto iss = [etv](TypeId t)
|
||||||
{
|
{
|
||||||
const ClassType* c = get<ClassType>(t);
|
const ExternType* c = get<ExternType>(t);
|
||||||
if (!c)
|
if (!c)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return isSubclass(ctv, c);
|
return isSubclass(etv, c);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!std::any_of(otherNegations.begin(), otherNegations.end(), iss))
|
if (!std::any_of(otherNegations.begin(), otherNegations.end(), iss))
|
||||||
|
@ -847,7 +847,7 @@ static void assertInvariant(const NormalizedType& norm)
|
||||||
|
|
||||||
LUAU_ASSERT(isNormalizedTop(norm.tops));
|
LUAU_ASSERT(isNormalizedTop(norm.tops));
|
||||||
LUAU_ASSERT(isNormalizedBoolean(norm.booleans));
|
LUAU_ASSERT(isNormalizedBoolean(norm.booleans));
|
||||||
LUAU_ASSERT(areNormalizedClasses(norm.classes));
|
LUAU_ASSERT(areNormalizedExternTypes(norm.externTypes));
|
||||||
LUAU_ASSERT(isNormalizedError(norm.errors));
|
LUAU_ASSERT(isNormalizedError(norm.errors));
|
||||||
LUAU_ASSERT(isNormalizedNil(norm.nils));
|
LUAU_ASSERT(isNormalizedNil(norm.nils));
|
||||||
LUAU_ASSERT(isNormalizedNumber(norm.numbers));
|
LUAU_ASSERT(isNormalizedNumber(norm.numbers));
|
||||||
|
@ -988,7 +988,7 @@ void Normalizer::clearNormal(NormalizedType& norm)
|
||||||
{
|
{
|
||||||
norm.tops = builtinTypes->neverType;
|
norm.tops = builtinTypes->neverType;
|
||||||
norm.booleans = builtinTypes->neverType;
|
norm.booleans = builtinTypes->neverType;
|
||||||
norm.classes.resetToNever();
|
norm.externTypes.resetToNever();
|
||||||
norm.errors = builtinTypes->neverType;
|
norm.errors = builtinTypes->neverType;
|
||||||
norm.nils = builtinTypes->neverType;
|
norm.nils = builtinTypes->neverType;
|
||||||
norm.numbers = builtinTypes->neverType;
|
norm.numbers = builtinTypes->neverType;
|
||||||
|
@ -1138,17 +1138,17 @@ TypeId Normalizer::unionOfBools(TypeId here, TypeId there)
|
||||||
return builtinTypes->booleanType;
|
return builtinTypes->booleanType;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Normalizer::unionClassesWithClass(TypeIds& heres, TypeId there)
|
void Normalizer::unionExternTypesWithExternType(TypeIds& heres, TypeId there)
|
||||||
{
|
{
|
||||||
if (heres.count(there))
|
if (heres.count(there))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const ClassType* tctv = get<ClassType>(there);
|
const ExternType* tctv = get<ExternType>(there);
|
||||||
|
|
||||||
for (auto it = heres.begin(); it != heres.end();)
|
for (auto it = heres.begin(); it != heres.end();)
|
||||||
{
|
{
|
||||||
TypeId here = *it;
|
TypeId here = *it;
|
||||||
const ClassType* hctv = get<ClassType>(here);
|
const ExternType* hctv = get<ExternType>(here);
|
||||||
if (isSubclass(tctv, hctv))
|
if (isSubclass(tctv, hctv))
|
||||||
return;
|
return;
|
||||||
else if (isSubclass(hctv, tctv))
|
else if (isSubclass(hctv, tctv))
|
||||||
|
@ -1160,16 +1160,16 @@ void Normalizer::unionClassesWithClass(TypeIds& heres, TypeId there)
|
||||||
heres.insert(there);
|
heres.insert(there);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Normalizer::unionClasses(TypeIds& heres, const TypeIds& theres)
|
void Normalizer::unionExternTypes(TypeIds& heres, const TypeIds& theres)
|
||||||
{
|
{
|
||||||
for (TypeId there : theres)
|
for (TypeId there : theres)
|
||||||
unionClassesWithClass(heres, there);
|
unionExternTypesWithExternType(heres, there);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool isSubclass(TypeId test, TypeId parent)
|
static bool isSubclass(TypeId test, TypeId parent)
|
||||||
{
|
{
|
||||||
const ClassType* testCtv = get<ClassType>(test);
|
const ExternType* testCtv = get<ExternType>(test);
|
||||||
const ClassType* parentCtv = get<ClassType>(parent);
|
const ExternType* parentCtv = get<ExternType>(parent);
|
||||||
|
|
||||||
LUAU_ASSERT(testCtv);
|
LUAU_ASSERT(testCtv);
|
||||||
LUAU_ASSERT(parentCtv);
|
LUAU_ASSERT(parentCtv);
|
||||||
|
@ -1177,12 +1177,12 @@ static bool isSubclass(TypeId test, TypeId parent)
|
||||||
return isSubclass(testCtv, parentCtv);
|
return isSubclass(testCtv, parentCtv);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Normalizer::unionClassesWithClass(NormalizedClassType& heres, TypeId there)
|
void Normalizer::unionExternTypesWithExternType(NormalizedExternType& heres, TypeId there)
|
||||||
{
|
{
|
||||||
for (auto it = heres.ordering.begin(); it != heres.ordering.end();)
|
for (auto it = heres.ordering.begin(); it != heres.ordering.end();)
|
||||||
{
|
{
|
||||||
TypeId hereTy = *it;
|
TypeId hereTy = *it;
|
||||||
TypeIds& hereNegations = heres.classes.at(hereTy);
|
TypeIds& hereNegations = heres.externTypes.at(hereTy);
|
||||||
|
|
||||||
// If the incoming class is a subclass of another class in the map, we
|
// If the incoming class is a subclass of another class in the map, we
|
||||||
// must ensure that it is negated by one of the negations in the same
|
// must ensure that it is negated by one of the negations in the same
|
||||||
|
@ -1204,7 +1204,7 @@ void Normalizer::unionClassesWithClass(NormalizedClassType& heres, TypeId there)
|
||||||
}
|
}
|
||||||
// If the incoming class is a superclass of one of the
|
// If the incoming class is a superclass of one of the
|
||||||
// negations, then the negation no longer applies and must be
|
// negations, then the negation no longer applies and must be
|
||||||
// removed. This is also true if they are equal. Since classes
|
// removed. This is also true if they are equal. Since extern types
|
||||||
// are, at this time, entirely persistent (we do not clone
|
// are, at this time, entirely persistent (we do not clone
|
||||||
// them), a pointer identity check is sufficient.
|
// them), a pointer identity check is sufficient.
|
||||||
else if (isSubclass(hereNegation, there))
|
else if (isSubclass(hereNegation, there))
|
||||||
|
@ -1231,7 +1231,7 @@ void Normalizer::unionClassesWithClass(NormalizedClassType& heres, TypeId there)
|
||||||
{
|
{
|
||||||
TypeIds negations = std::move(hereNegations);
|
TypeIds negations = std::move(hereNegations);
|
||||||
it = heres.ordering.erase(it);
|
it = heres.ordering.erase(it);
|
||||||
heres.classes.erase(hereTy);
|
heres.externTypes.erase(hereTy);
|
||||||
|
|
||||||
heres.pushPair(there, std::move(negations));
|
heres.pushPair(there, std::move(negations));
|
||||||
return;
|
return;
|
||||||
|
@ -1248,10 +1248,10 @@ void Normalizer::unionClassesWithClass(NormalizedClassType& heres, TypeId there)
|
||||||
heres.pushPair(there, TypeIds{});
|
heres.pushPair(there, TypeIds{});
|
||||||
}
|
}
|
||||||
|
|
||||||
void Normalizer::unionClasses(NormalizedClassType& heres, const NormalizedClassType& theres)
|
void Normalizer::unionExternTypes(NormalizedExternType& heres, const NormalizedExternType& theres)
|
||||||
{
|
{
|
||||||
// This method bears much similarity with unionClassesWithClass, but is
|
// This method bears much similarity with unionExternTypesWithExternType, but is
|
||||||
// solving a more general problem. In unionClassesWithClass, we are dealing
|
// solving a more general problem. In unionExternTypesWithExternType, we are dealing
|
||||||
// with a singular positive type. Since it's one type, we can use early
|
// with a singular positive type. Since it's one type, we can use early
|
||||||
// returns as control flow. Since it's guaranteed to be positive, we do not
|
// returns as control flow. Since it's guaranteed to be positive, we do not
|
||||||
// have negations to worry about combining. The two aspects combine to make
|
// have negations to worry about combining. The two aspects combine to make
|
||||||
|
@ -1260,9 +1260,9 @@ void Normalizer::unionClasses(NormalizedClassType& heres, const NormalizedClassT
|
||||||
|
|
||||||
for (const TypeId thereTy : theres.ordering)
|
for (const TypeId thereTy : theres.ordering)
|
||||||
{
|
{
|
||||||
const TypeIds& thereNegations = theres.classes.at(thereTy);
|
const TypeIds& thereNegations = theres.externTypes.at(thereTy);
|
||||||
|
|
||||||
// If it happens that there are _no_ classes in the current map, or the
|
// If it happens that there are _no_ extern types in the current map, or the
|
||||||
// incoming class is completely unrelated to any class in the current
|
// incoming class is completely unrelated to any class in the current
|
||||||
// map, we must insert the incoming pair as-is.
|
// map, we must insert the incoming pair as-is.
|
||||||
bool insert = true;
|
bool insert = true;
|
||||||
|
@ -1270,7 +1270,7 @@ void Normalizer::unionClasses(NormalizedClassType& heres, const NormalizedClassT
|
||||||
for (auto it = heres.ordering.begin(); it != heres.ordering.end();)
|
for (auto it = heres.ordering.begin(); it != heres.ordering.end();)
|
||||||
{
|
{
|
||||||
TypeId hereTy = *it;
|
TypeId hereTy = *it;
|
||||||
TypeIds& hereNegations = heres.classes.at(hereTy);
|
TypeIds& hereNegations = heres.externTypes.at(hereTy);
|
||||||
|
|
||||||
if (isSubclass(thereTy, hereTy))
|
if (isSubclass(thereTy, hereTy))
|
||||||
{
|
{
|
||||||
|
@ -1294,7 +1294,7 @@ void Normalizer::unionClasses(NormalizedClassType& heres, const NormalizedClassT
|
||||||
// If the incoming class is a superclass of one of the
|
// If the incoming class is a superclass of one of the
|
||||||
// negations, then the negation no longer applies and must
|
// negations, then the negation no longer applies and must
|
||||||
// be removed. This is also true if they are equal. Since
|
// be removed. This is also true if they are equal. Since
|
||||||
// classes are, at this time, entirely persistent (we do not
|
// extern types are, at this time, entirely persistent (we do not
|
||||||
// clone them), a pointer identity check is sufficient.
|
// clone them), a pointer identity check is sufficient.
|
||||||
else if (isSubclass(hereNegateTy, thereTy))
|
else if (isSubclass(hereNegateTy, thereTy))
|
||||||
{
|
{
|
||||||
|
@ -1319,17 +1319,17 @@ void Normalizer::unionClasses(NormalizedClassType& heres, const NormalizedClassT
|
||||||
else if (isSubclass(hereTy, thereTy))
|
else if (isSubclass(hereTy, thereTy))
|
||||||
{
|
{
|
||||||
TypeIds negations = std::move(hereNegations);
|
TypeIds negations = std::move(hereNegations);
|
||||||
unionClasses(negations, thereNegations);
|
unionExternTypes(negations, thereNegations);
|
||||||
|
|
||||||
it = heres.ordering.erase(it);
|
it = heres.ordering.erase(it);
|
||||||
heres.classes.erase(hereTy);
|
heres.externTypes.erase(hereTy);
|
||||||
heres.pushPair(thereTy, std::move(negations));
|
heres.pushPair(thereTy, std::move(negations));
|
||||||
insert = false;
|
insert = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
else if (hereTy == thereTy)
|
else if (hereTy == thereTy)
|
||||||
{
|
{
|
||||||
unionClasses(hereNegations, thereNegations);
|
unionExternTypes(hereNegations, thereNegations);
|
||||||
insert = false;
|
insert = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -1685,8 +1685,12 @@ NormalizationResult Normalizer::unionNormals(NormalizedType& here, const Normali
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Limit based on worst-case expansion of the function unions
|
||||||
|
if (here.functions.parts.size() * there.functions.parts.size() >= size_t(FInt::LuauNormalizeUnionLimit))
|
||||||
|
return NormalizationResult::HitLimits;
|
||||||
|
|
||||||
here.booleans = unionOfBools(here.booleans, there.booleans);
|
here.booleans = unionOfBools(here.booleans, there.booleans);
|
||||||
unionClasses(here.classes, there.classes);
|
unionExternTypes(here.externTypes, there.externTypes);
|
||||||
|
|
||||||
here.errors = (get<NeverType>(there.errors) ? here.errors : there.errors);
|
here.errors = (get<NeverType>(there.errors) ? here.errors : there.errors);
|
||||||
here.nils = (get<NeverType>(there.nils) ? here.nils : there.nils);
|
here.nils = (get<NeverType>(there.nils) ? here.nils : there.nils);
|
||||||
|
@ -1696,6 +1700,7 @@ NormalizationResult Normalizer::unionNormals(NormalizedType& here, const Normali
|
||||||
here.buffers = (get<NeverType>(there.buffers) ? here.buffers : there.buffers);
|
here.buffers = (get<NeverType>(there.buffers) ? here.buffers : there.buffers);
|
||||||
unionFunctions(here.functions, there.functions);
|
unionFunctions(here.functions, there.functions);
|
||||||
unionTables(here.tables, there.tables);
|
unionTables(here.tables, there.tables);
|
||||||
|
|
||||||
return NormalizationResult::True;
|
return NormalizationResult::True;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1735,7 +1740,7 @@ NormalizationResult Normalizer::intersectNormalWithNegationTy(TypeId toNegate, N
|
||||||
return NormalizationResult::True;
|
return NormalizationResult::True;
|
||||||
}
|
}
|
||||||
|
|
||||||
// See above for an explaination of `ignoreSmallerTyvars`.
|
// See above for an explanation of `ignoreSmallerTyvars`.
|
||||||
NormalizationResult Normalizer::unionNormalWithTy(
|
NormalizationResult Normalizer::unionNormalWithTy(
|
||||||
NormalizedType& here,
|
NormalizedType& here,
|
||||||
TypeId there,
|
TypeId there,
|
||||||
|
@ -1809,7 +1814,8 @@ NormalizationResult Normalizer::unionNormalWithTy(
|
||||||
}
|
}
|
||||||
else if (get<UnknownType>(here.tops))
|
else if (get<UnknownType>(here.tops))
|
||||||
return NormalizationResult::True;
|
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)
|
if (tyvarIndex(there) <= ignoreSmallerTyvars)
|
||||||
return NormalizationResult::True;
|
return NormalizationResult::True;
|
||||||
|
@ -1824,8 +1830,8 @@ NormalizationResult Normalizer::unionNormalWithTy(
|
||||||
unionFunctionsWithFunction(here.functions, there);
|
unionFunctionsWithFunction(here.functions, there);
|
||||||
else if (get<TableType>(there) || get<MetatableType>(there))
|
else if (get<TableType>(there) || get<MetatableType>(there))
|
||||||
unionTablesWithTable(here.tables, there);
|
unionTablesWithTable(here.tables, there);
|
||||||
else if (get<ClassType>(there))
|
else if (get<ExternType>(there))
|
||||||
unionClassesWithClass(here.classes, there);
|
unionExternTypesWithExternType(here.externTypes, there);
|
||||||
else if (get<ErrorType>(there))
|
else if (get<ErrorType>(there))
|
||||||
here.errors = there;
|
here.errors = there;
|
||||||
else if (const PrimitiveType* ptv = get<PrimitiveType>(there))
|
else if (const PrimitiveType* ptv = get<PrimitiveType>(there))
|
||||||
|
@ -1938,29 +1944,29 @@ std::optional<NormalizedType> Normalizer::negateNormal(const NormalizedType& her
|
||||||
result.booleans = builtinTypes->trueType;
|
result.booleans = builtinTypes->trueType;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (here.classes.isNever())
|
if (here.externTypes.isNever())
|
||||||
{
|
{
|
||||||
resetToTop(builtinTypes, result.classes);
|
resetToTop(builtinTypes, result.externTypes);
|
||||||
}
|
}
|
||||||
else if (isTop(builtinTypes, result.classes))
|
else if (isTop(builtinTypes, result.externTypes))
|
||||||
{
|
{
|
||||||
result.classes.resetToNever();
|
result.externTypes.resetToNever();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
TypeIds rootNegations{};
|
TypeIds rootNegations{};
|
||||||
|
|
||||||
for (const auto& [hereParent, hereNegations] : here.classes.classes)
|
for (const auto& [hereParent, hereNegations] : here.externTypes.externTypes)
|
||||||
{
|
{
|
||||||
if (hereParent != builtinTypes->classType)
|
if (hereParent != builtinTypes->externType)
|
||||||
rootNegations.insert(hereParent);
|
rootNegations.insert(hereParent);
|
||||||
|
|
||||||
for (TypeId hereNegation : hereNegations)
|
for (TypeId hereNegation : hereNegations)
|
||||||
unionClassesWithClass(result.classes, hereNegation);
|
unionExternTypesWithExternType(result.externTypes, hereNegation);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!rootNegations.empty())
|
if (!rootNegations.empty())
|
||||||
result.classes.pushPair(builtinTypes->classType, rootNegations);
|
result.externTypes.pushPair(builtinTypes->externType, rootNegations);
|
||||||
}
|
}
|
||||||
|
|
||||||
result.nils = get<NeverType>(here.nils) ? builtinTypes->nilType : builtinTypes->neverType;
|
result.nils = get<NeverType>(here.nils) ? builtinTypes->nilType : builtinTypes->neverType;
|
||||||
|
@ -2138,7 +2144,7 @@ TypeId Normalizer::intersectionOfBools(TypeId here, TypeId there)
|
||||||
return there;
|
return there;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Normalizer::intersectClasses(NormalizedClassType& heres, const NormalizedClassType& theres)
|
void Normalizer::intersectExternTypes(NormalizedExternType& heres, const NormalizedExternType& theres)
|
||||||
{
|
{
|
||||||
if (theres.isNever())
|
if (theres.isNever())
|
||||||
{
|
{
|
||||||
|
@ -2172,12 +2178,12 @@ void Normalizer::intersectClasses(NormalizedClassType& heres, const NormalizedCl
|
||||||
// declare the result of the intersection operation to be never.
|
// declare the result of the intersection operation to be never.
|
||||||
for (const TypeId thereTy : theres.ordering)
|
for (const TypeId thereTy : theres.ordering)
|
||||||
{
|
{
|
||||||
const TypeIds& thereNegations = theres.classes.at(thereTy);
|
const TypeIds& thereNegations = theres.externTypes.at(thereTy);
|
||||||
|
|
||||||
for (auto it = heres.ordering.begin(); it != heres.ordering.end();)
|
for (auto it = heres.ordering.begin(); it != heres.ordering.end();)
|
||||||
{
|
{
|
||||||
TypeId hereTy = *it;
|
TypeId hereTy = *it;
|
||||||
TypeIds& hereNegations = heres.classes.at(hereTy);
|
TypeIds& hereNegations = heres.externTypes.at(hereTy);
|
||||||
|
|
||||||
if (isSubclass(thereTy, hereTy))
|
if (isSubclass(thereTy, hereTy))
|
||||||
{
|
{
|
||||||
|
@ -2200,10 +2206,10 @@ void Normalizer::intersectClasses(NormalizedClassType& heres, const NormalizedCl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unionClasses(negations, thereNegations);
|
unionExternTypes(negations, thereNegations);
|
||||||
|
|
||||||
it = heres.ordering.erase(it);
|
it = heres.ordering.erase(it);
|
||||||
heres.classes.erase(hereTy);
|
heres.externTypes.erase(hereTy);
|
||||||
heres.pushPair(thereTy, std::move(negations));
|
heres.pushPair(thereTy, std::move(negations));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -2228,15 +2234,15 @@ void Normalizer::intersectClasses(NormalizedClassType& heres, const NormalizedCl
|
||||||
{
|
{
|
||||||
if (isSubclass(hereTy, *nIt))
|
if (isSubclass(hereTy, *nIt))
|
||||||
{
|
{
|
||||||
// eg SomeClass & (class & ~SomeClass)
|
// eg SomeExternType & (class & ~SomeExternType)
|
||||||
// or SomeClass & (class & ~ParentClass)
|
// or SomeExternType & (class & ~ParentExternType)
|
||||||
heres.classes.erase(hereTy);
|
heres.externTypes.erase(hereTy);
|
||||||
it = heres.ordering.erase(it);
|
it = heres.ordering.erase(it);
|
||||||
erasedHere = true;
|
erasedHere = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// eg SomeClass & (class & ~Unrelated)
|
// eg SomeExternType & (class & ~Unrelated)
|
||||||
if (!isSubclass(*nIt, hereTy))
|
if (!isSubclass(*nIt, hereTy))
|
||||||
nIt = negations.erase(nIt);
|
nIt = negations.erase(nIt);
|
||||||
else
|
else
|
||||||
|
@ -2245,30 +2251,30 @@ void Normalizer::intersectClasses(NormalizedClassType& heres, const NormalizedCl
|
||||||
|
|
||||||
if (!erasedHere)
|
if (!erasedHere)
|
||||||
{
|
{
|
||||||
unionClasses(hereNegations, negations);
|
unionExternTypes(hereNegations, negations);
|
||||||
++it;
|
++it;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (hereTy == thereTy)
|
else if (hereTy == thereTy)
|
||||||
{
|
{
|
||||||
unionClasses(hereNegations, thereNegations);
|
unionExternTypes(hereNegations, thereNegations);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
it = heres.ordering.erase(it);
|
it = heres.ordering.erase(it);
|
||||||
heres.classes.erase(hereTy);
|
heres.externTypes.erase(hereTy);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Normalizer::intersectClassesWithClass(NormalizedClassType& heres, TypeId there)
|
void Normalizer::intersectExternTypesWithExternType(NormalizedExternType& heres, TypeId there)
|
||||||
{
|
{
|
||||||
for (auto it = heres.ordering.begin(); it != heres.ordering.end();)
|
for (auto it = heres.ordering.begin(); it != heres.ordering.end();)
|
||||||
{
|
{
|
||||||
TypeId hereTy = *it;
|
TypeId hereTy = *it;
|
||||||
const TypeIds& hereNegations = heres.classes.at(hereTy);
|
const TypeIds& hereNegations = heres.externTypes.at(hereTy);
|
||||||
|
|
||||||
// If the incoming class _is_ the current class, we skip it. Maybe
|
// If the incoming class _is_ the current class, we skip it. Maybe
|
||||||
// another entry will have a different story. We check for this first
|
// another entry will have a different story. We check for this first
|
||||||
|
@ -2284,9 +2290,24 @@ void Normalizer::intersectClassesWithClass(NormalizedClassType& heres, TypeId th
|
||||||
else if (isSubclass(there, hereTy))
|
else if (isSubclass(there, hereTy))
|
||||||
{
|
{
|
||||||
TypeIds negations = std::move(hereNegations);
|
TypeIds negations = std::move(hereNegations);
|
||||||
|
bool emptyIntersectWithNegation = false;
|
||||||
|
|
||||||
for (auto nIt = negations.begin(); nIt != negations.end();)
|
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))
|
if (!isSubclass(*nIt, there))
|
||||||
{
|
{
|
||||||
nIt = negations.erase(nIt);
|
nIt = negations.erase(nIt);
|
||||||
|
@ -2298,8 +2319,9 @@ void Normalizer::intersectClassesWithClass(NormalizedClassType& heres, TypeId th
|
||||||
}
|
}
|
||||||
|
|
||||||
it = heres.ordering.erase(it);
|
it = heres.ordering.erase(it);
|
||||||
heres.classes.erase(hereTy);
|
heres.externTypes.erase(hereTy);
|
||||||
heres.pushPair(there, std::move(negations));
|
if (!emptyIntersectWithNegation)
|
||||||
|
heres.pushPair(there, std::move(negations));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// If the incoming class is a superclass of the current class, we don't
|
// 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
|
else
|
||||||
{
|
{
|
||||||
it = heres.ordering.erase(it);
|
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 (tprop.readTy.has_value())
|
||||||
{
|
{
|
||||||
// if the intersection of the read types of a property is uninhabited, the whole table is `never`.
|
if (FFlag::LuauFixInfiniteRecursionInNormalization)
|
||||||
// We've seen these table prop elements before and we're about to ask if their intersection
|
|
||||||
// is inhabited
|
|
||||||
if (FFlag::LuauNormalizationTracksCyclicPairsThroughInhabitance)
|
|
||||||
{
|
{
|
||||||
|
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 pair1 = std::pair{*hprop.readTy, *tprop.readTy};
|
||||||
auto pair2 = std::pair{*tprop.readTy, *hprop.readTy};
|
auto pair2 = std::pair{*tprop.readTy, *hprop.readTy};
|
||||||
if (seenTablePropPairs.contains(pair1) || seenTablePropPairs.contains(pair2))
|
if (seenTablePropPairs.contains(pair1) || seenTablePropPairs.contains(pair2))
|
||||||
|
@ -2603,6 +2645,8 @@ std::optional<TypeId> Normalizer::intersectionOfTables(TypeId here, TypeId there
|
||||||
seenTablePropPairs.insert(pair2);
|
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};
|
Set<TypeId> seenSet{nullptr};
|
||||||
NormalizationResult res = isIntersectionInhabited(*hprop.readTy, *tprop.readTy, seenTablePropPairs, seenSet);
|
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);
|
hereSubThere &= (ty == hprop.readTy);
|
||||||
thereSubHere &= (ty == tprop.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
|
else
|
||||||
{
|
{
|
||||||
|
@ -3039,15 +3055,12 @@ NormalizationResult Normalizer::intersectTyvarsWithTy(
|
||||||
return NormalizationResult::True;
|
return NormalizationResult::True;
|
||||||
}
|
}
|
||||||
|
|
||||||
// See above for an explaination of `ignoreSmallerTyvars`.
|
// See above for an explanation of `ignoreSmallerTyvars`.
|
||||||
NormalizationResult Normalizer::intersectNormals(NormalizedType& here, const NormalizedType& there, int ignoreSmallerTyvars)
|
NormalizationResult Normalizer::intersectNormals(NormalizedType& here, const NormalizedType& there, int ignoreSmallerTyvars)
|
||||||
{
|
{
|
||||||
if (FFlag::LuauIntersectNormalsNeedsToTrackResourceLimits)
|
RecursionCounter _rc(&sharedState->counters.recursionCount);
|
||||||
{
|
if (!withinResourceLimits())
|
||||||
RecursionCounter _rc(&sharedState->counters.recursionCount);
|
return NormalizationResult::HitLimits;
|
||||||
if (!withinResourceLimits())
|
|
||||||
return NormalizationResult::HitLimits;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!get<NeverType>(there.tops))
|
if (!get<NeverType>(there.tops))
|
||||||
{
|
{
|
||||||
|
@ -3060,14 +3073,17 @@ NormalizationResult Normalizer::intersectNormals(NormalizedType& here, const Nor
|
||||||
return unionNormals(here, there, ignoreSmallerTyvars);
|
return unionNormals(here, there, ignoreSmallerTyvars);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Limit based on worst-case expansion of the table intersection
|
// Limit based on worst-case expansion of the table/function intersections
|
||||||
// This restriction can be relaxed when table intersection simplification is improved
|
// This restriction can be relaxed when table intersection simplification is improved
|
||||||
if (here.tables.size() * there.tables.size() >= size_t(FInt::LuauNormalizeIntersectionLimit))
|
if (here.tables.size() * there.tables.size() >= size_t(FInt::LuauNormalizeIntersectionLimit))
|
||||||
return NormalizationResult::HitLimits;
|
return NormalizationResult::HitLimits;
|
||||||
|
|
||||||
|
if (here.functions.parts.size() * there.functions.parts.size() >= size_t(FInt::LuauNormalizeIntersectionLimit))
|
||||||
|
return NormalizationResult::HitLimits;
|
||||||
|
|
||||||
here.booleans = intersectionOfBools(here.booleans, there.booleans);
|
here.booleans = intersectionOfBools(here.booleans, there.booleans);
|
||||||
|
|
||||||
intersectClasses(here.classes, there.classes);
|
intersectExternTypes(here.externTypes, there.externTypes);
|
||||||
here.errors = (get<NeverType>(there.errors) ? there.errors : here.errors);
|
here.errors = (get<NeverType>(there.errors) ? there.errors : here.errors);
|
||||||
here.nils = (get<NeverType>(there.nils) ? there.nils : here.nils);
|
here.nils = (get<NeverType>(there.nils) ? there.nils : here.nils);
|
||||||
here.numbers = (get<NeverType>(there.numbers) ? there.numbers : here.numbers);
|
here.numbers = (get<NeverType>(there.numbers) ? there.numbers : here.numbers);
|
||||||
|
@ -3162,7 +3178,8 @@ NormalizationResult Normalizer::intersectNormalWithTy(
|
||||||
}
|
}
|
||||||
return NormalizationResult::True;
|
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 thereNorm{builtinTypes};
|
||||||
NormalizedType topNorm{builtinTypes};
|
NormalizedType topNorm{builtinTypes};
|
||||||
|
@ -3188,18 +3205,18 @@ NormalizationResult Normalizer::intersectNormalWithTy(
|
||||||
intersectTablesWithTable(tables, there, seenTablePropPairs, seenSetTypes);
|
intersectTablesWithTable(tables, there, seenTablePropPairs, seenSetTypes);
|
||||||
here.tables = std::move(tables);
|
here.tables = std::move(tables);
|
||||||
}
|
}
|
||||||
else if (get<ClassType>(there))
|
else if (get<ExternType>(there))
|
||||||
{
|
{
|
||||||
NormalizedClassType nct = std::move(here.classes);
|
NormalizedExternType nct = std::move(here.externTypes);
|
||||||
clearNormal(here);
|
clearNormal(here);
|
||||||
intersectClassesWithClass(nct, there);
|
intersectExternTypesWithExternType(nct, there);
|
||||||
here.classes = std::move(nct);
|
here.externTypes = std::move(nct);
|
||||||
}
|
}
|
||||||
else if (get<ErrorType>(there))
|
else if (get<ErrorType>(there))
|
||||||
{
|
{
|
||||||
TypeId errors = here.errors;
|
TypeId errors = here.errors;
|
||||||
clearNormal(here);
|
clearNormal(here);
|
||||||
here.errors = errors;
|
here.errors = get<ErrorType>(errors) ? errors : there;
|
||||||
}
|
}
|
||||||
else if (const PrimitiveType* ptv = get<PrimitiveType>(there))
|
else if (const PrimitiveType* ptv = get<PrimitiveType>(there))
|
||||||
{
|
{
|
||||||
|
@ -3257,7 +3274,7 @@ NormalizationResult Normalizer::intersectNormalWithTy(
|
||||||
subtractPrimitive(here, ntv->ty);
|
subtractPrimitive(here, ntv->ty);
|
||||||
else if (const SingletonType* stv = get<SingletonType>(t))
|
else if (const SingletonType* stv = get<SingletonType>(t))
|
||||||
subtractSingleton(here, follow(ntv->ty));
|
subtractSingleton(here, follow(ntv->ty));
|
||||||
else if (get<ClassType>(t))
|
else if (get<ExternType>(t))
|
||||||
{
|
{
|
||||||
NormalizationResult res = intersectNormalWithNegationTy(t, here);
|
NormalizationResult res = intersectNormalWithNegationTy(t, here);
|
||||||
if (shouldEarlyExit(res))
|
if (shouldEarlyExit(res))
|
||||||
|
@ -3296,8 +3313,18 @@ NormalizationResult Normalizer::intersectNormalWithTy(
|
||||||
clearNormal(here);
|
clearNormal(here);
|
||||||
return NormalizationResult::True;
|
return NormalizationResult::True;
|
||||||
}
|
}
|
||||||
|
else if (get<ErrorType>(t))
|
||||||
|
{
|
||||||
|
// ~error is still an error, so intersecting with the negation is the same as intersecting with a type
|
||||||
|
TypeId errors = here.errors;
|
||||||
|
clearNormal(here);
|
||||||
|
here.errors = get<ErrorType>(errors) ? errors : t;
|
||||||
|
}
|
||||||
else if (auto nt = get<NegationType>(t))
|
else if (auto nt = get<NegationType>(t))
|
||||||
|
{
|
||||||
|
here.tyvars = std::move(tyvars);
|
||||||
return intersectNormalWithTy(here, nt->ty, seenTablePropPairs, seenSetTypes);
|
return intersectNormalWithTy(here, nt->ty, seenTablePropPairs, seenSetTypes);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// TODO negated unions, intersections, table, and function.
|
// TODO negated unions, intersections, table, and function.
|
||||||
|
@ -3307,7 +3334,7 @@ NormalizationResult Normalizer::intersectNormalWithTy(
|
||||||
}
|
}
|
||||||
else if (get<NeverType>(there))
|
else if (get<NeverType>(there))
|
||||||
{
|
{
|
||||||
here.classes.resetToNever();
|
here.externTypes.resetToNever();
|
||||||
}
|
}
|
||||||
else if (get<NoRefineType>(there))
|
else if (get<NoRefineType>(there))
|
||||||
{
|
{
|
||||||
|
@ -3325,7 +3352,7 @@ NormalizationResult Normalizer::intersectNormalWithTy(
|
||||||
return NormalizationResult::True;
|
return NormalizationResult::True;
|
||||||
}
|
}
|
||||||
|
|
||||||
void makeTableShared(TypeId ty)
|
void makeTableShared_DEPRECATED(TypeId ty)
|
||||||
{
|
{
|
||||||
ty = follow(ty);
|
ty = follow(ty);
|
||||||
if (auto tableTy = getMutable<TableType>(ty))
|
if (auto tableTy = getMutable<TableType>(ty))
|
||||||
|
@ -3335,11 +3362,35 @@ void makeTableShared(TypeId ty)
|
||||||
}
|
}
|
||||||
else if (auto metatableTy = get<MetatableType>(ty))
|
else if (auto metatableTy = get<MetatableType>(ty))
|
||||||
{
|
{
|
||||||
makeTableShared(metatableTy->metatable);
|
makeTableShared_DEPRECATED(metatableTy->metatable);
|
||||||
makeTableShared(metatableTy->table);
|
makeTableShared_DEPRECATED(metatableTy->table);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void makeTableShared(TypeId ty, DenseHashSet<TypeId>& seen)
|
||||||
|
{
|
||||||
|
ty = follow(ty);
|
||||||
|
if (seen.contains(ty))
|
||||||
|
return;
|
||||||
|
seen.insert(ty);
|
||||||
|
if (auto tableTy = getMutable<TableType>(ty))
|
||||||
|
{
|
||||||
|
for (auto& [_, prop] : tableTy->props)
|
||||||
|
prop.makeShared();
|
||||||
|
}
|
||||||
|
else if (auto metatableTy = get<MetatableType>(ty))
|
||||||
|
{
|
||||||
|
makeTableShared(metatableTy->metatable, seen);
|
||||||
|
makeTableShared(metatableTy->table, seen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void makeTableShared(TypeId ty)
|
||||||
|
{
|
||||||
|
DenseHashSet<TypeId> seen{nullptr};
|
||||||
|
makeTableShared(ty, seen);
|
||||||
|
}
|
||||||
|
|
||||||
// -------- Convert back from a normalized type to a type
|
// -------- Convert back from a normalized type to a type
|
||||||
TypeId Normalizer::typeFromNormal(const NormalizedType& norm)
|
TypeId Normalizer::typeFromNormal(const NormalizedType& norm)
|
||||||
{
|
{
|
||||||
|
@ -3352,18 +3403,18 @@ TypeId Normalizer::typeFromNormal(const NormalizedType& norm)
|
||||||
if (!get<NeverType>(norm.booleans))
|
if (!get<NeverType>(norm.booleans))
|
||||||
result.push_back(norm.booleans);
|
result.push_back(norm.booleans);
|
||||||
|
|
||||||
if (isTop(builtinTypes, norm.classes))
|
if (isTop(builtinTypes, norm.externTypes))
|
||||||
{
|
{
|
||||||
result.push_back(builtinTypes->classType);
|
result.push_back(builtinTypes->externType);
|
||||||
}
|
}
|
||||||
else if (!norm.classes.isNever())
|
else if (!norm.externTypes.isNever())
|
||||||
{
|
{
|
||||||
std::vector<TypeId> parts;
|
std::vector<TypeId> parts;
|
||||||
parts.reserve(norm.classes.classes.size());
|
parts.reserve(norm.externTypes.externTypes.size());
|
||||||
|
|
||||||
for (const TypeId normTy : norm.classes.ordering)
|
for (const TypeId normTy : norm.externTypes.ordering)
|
||||||
{
|
{
|
||||||
const TypeIds& normNegations = norm.classes.classes.at(normTy);
|
const TypeIds& normNegations = norm.externTypes.externTypes.at(normTy);
|
||||||
|
|
||||||
if (normNegations.empty())
|
if (normNegations.empty())
|
||||||
{
|
{
|
||||||
|
@ -3439,7 +3490,10 @@ TypeId Normalizer::typeFromNormal(const NormalizedType& norm)
|
||||||
result.reserve(result.size() + norm.tables.size());
|
result.reserve(result.size() + norm.tables.size());
|
||||||
for (auto table : norm.tables)
|
for (auto table : norm.tables)
|
||||||
{
|
{
|
||||||
makeTableShared(table);
|
if (FFlag::LuauNormalizationCatchMetatableCycles)
|
||||||
|
makeTableShared(table);
|
||||||
|
else
|
||||||
|
makeTableShared_DEPRECATED(table);
|
||||||
result.push_back(table);
|
result.push_back(table);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3465,7 +3519,14 @@ TypeId Normalizer::typeFromNormal(const NormalizedType& norm)
|
||||||
return arena->addType(UnionType{std::move(result)});
|
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};
|
UnifierSharedState sharedState{&ice};
|
||||||
TypeArena arena;
|
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!
|
// Subtyping under DCR is not implemented using unification!
|
||||||
if (FFlag::LuauSolverV2)
|
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;
|
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};
|
UnifierSharedState sharedState{&ice};
|
||||||
TypeArena arena;
|
TypeArena arena;
|
||||||
|
@ -3504,7 +3572,7 @@ bool isSubtype(TypePackId subPack, TypePackId superPack, NotNull<Scope> scope, N
|
||||||
// Subtyping under DCR is not implemented using unification!
|
// Subtyping under DCR is not implemented using unification!
|
||||||
if (FFlag::LuauSolverV2)
|
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;
|
return subtyping.isSubtype(subPack, superPack, scope).isSubtype;
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,12 +10,15 @@
|
||||||
#include "Luau/TypeUtils.h"
|
#include "Luau/TypeUtils.h"
|
||||||
#include "Luau/Unifier2.h"
|
#include "Luau/Unifier2.h"
|
||||||
|
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauArityMismatchOnUndersaturatedUnknownArguments)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
|
||||||
OverloadResolver::OverloadResolver(
|
OverloadResolver::OverloadResolver(
|
||||||
NotNull<BuiltinTypes> builtinTypes,
|
NotNull<BuiltinTypes> builtinTypes,
|
||||||
NotNull<TypeArena> arena,
|
NotNull<TypeArena> arena,
|
||||||
|
NotNull<Simplifier> simplifier,
|
||||||
NotNull<Normalizer> normalizer,
|
NotNull<Normalizer> normalizer,
|
||||||
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
|
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
|
||||||
NotNull<Scope> scope,
|
NotNull<Scope> scope,
|
||||||
|
@ -25,12 +28,13 @@ OverloadResolver::OverloadResolver(
|
||||||
)
|
)
|
||||||
: builtinTypes(builtinTypes)
|
: builtinTypes(builtinTypes)
|
||||||
, arena(arena)
|
, arena(arena)
|
||||||
|
, simplifier(simplifier)
|
||||||
, normalizer(normalizer)
|
, normalizer(normalizer)
|
||||||
, typeFunctionRuntime(typeFunctionRuntime)
|
, typeFunctionRuntime(typeFunctionRuntime)
|
||||||
, scope(scope)
|
, scope(scope)
|
||||||
, ice(reporter)
|
, ice(reporter)
|
||||||
, limits(limits)
|
, limits(limits)
|
||||||
, subtyping({builtinTypes, arena, normalizer, typeFunctionRuntime, ice})
|
, subtyping({builtinTypes, arena, simplifier, normalizer, typeFunctionRuntime, ice})
|
||||||
, callLoc(callLocation)
|
, callLoc(callLocation)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -202,7 +206,7 @@ std::pair<OverloadResolver::Analysis, ErrorVec> OverloadResolver::checkOverload_
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
FunctionGraphReductionResult result = reduceTypeFunctions(
|
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())
|
if (!result.errors.empty())
|
||||||
return {OverloadIsNonviable, result.errors};
|
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
|
// If any of the unsatisfied arguments are not supertypes of
|
||||||
// nil, then this overload does not match.
|
// nil or are `unknown`, then this overload does not match.
|
||||||
for (size_t i = firstUnsatisfiedArgument; i < requiredHead.size(); ++i)
|
for (size_t i = firstUnsatisfiedArgument; i < requiredHead.size(); ++i)
|
||||||
{
|
{
|
||||||
if (!subtyping.isSubtype(builtinTypes->nilType, requiredHead[i], scope).isSubtype)
|
if (FFlag::LuauArityMismatchOnUndersaturatedUnknownArguments)
|
||||||
{
|
{
|
||||||
auto [minParams, optMaxParams] = getParameterExtents(TxnLog::empty(), fn->argTypes);
|
if (get<UnknownType>(follow(requiredHead[i])) || !subtyping.isSubtype(builtinTypes->nilType, requiredHead[i], scope).isSubtype)
|
||||||
TypeError error{fnExpr->location, CountMismatch{minParams, optMaxParams, args->head.size(), CountMismatch::Arg, isVariadic}};
|
{
|
||||||
|
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`.
|
// 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.
|
// 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<BuiltinTypes> builtinTypes,
|
||||||
NotNull<TypeArena> arena,
|
NotNull<TypeArena> arena,
|
||||||
|
NotNull<Simplifier> simplifier,
|
||||||
NotNull<Normalizer> normalizer,
|
NotNull<Normalizer> normalizer,
|
||||||
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
|
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
|
||||||
NotNull<Scope> scope,
|
NotNull<Scope> scope,
|
||||||
|
@ -417,8 +439,9 @@ std::optional<TypeId> selectOverload(
|
||||||
TypePackId argsPack
|
TypePackId argsPack
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
OverloadResolver resolver{builtinTypes, arena, normalizer, typeFunctionRuntime, scope, iceReporter, limits, location};
|
auto resolver =
|
||||||
auto [status, overload] = resolver.selectOverload(fn, argsPack);
|
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)
|
if (status == OverloadResolver::Analysis::Ok)
|
||||||
return overload;
|
return overload;
|
||||||
|
@ -432,6 +455,7 @@ std::optional<TypeId> selectOverload(
|
||||||
SolveResult solveFunctionCall(
|
SolveResult solveFunctionCall(
|
||||||
NotNull<TypeArena> arena,
|
NotNull<TypeArena> arena,
|
||||||
NotNull<BuiltinTypes> builtinTypes,
|
NotNull<BuiltinTypes> builtinTypes,
|
||||||
|
NotNull<Simplifier> simplifier,
|
||||||
NotNull<Normalizer> normalizer,
|
NotNull<Normalizer> normalizer,
|
||||||
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
|
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
|
||||||
NotNull<InternalErrorReporter> iceReporter,
|
NotNull<InternalErrorReporter> iceReporter,
|
||||||
|
@ -443,22 +467,22 @@ SolveResult solveFunctionCall(
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
std::optional<TypeId> overloadToUse =
|
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)
|
if (!overloadToUse)
|
||||||
return {SolveResult::NoMatchingOverload};
|
return {SolveResult::NoMatchingOverload};
|
||||||
|
|
||||||
TypePackId resultPack = arena->freshTypePack(scope);
|
TypePackId resultPack = arena->freshTypePack(scope);
|
||||||
|
|
||||||
TypeId inferredTy = arena->addType(FunctionType{TypeLevel{}, scope.get(), argsPack, resultPack});
|
TypeId inferredTy = arena->addType(FunctionType{TypeLevel{}, argsPack, resultPack});
|
||||||
Unifier2 u2{NotNull{arena}, builtinTypes, scope, iceReporter};
|
Unifier2 u2{NotNull{arena}, builtinTypes, scope, iceReporter};
|
||||||
|
|
||||||
const bool occursCheckPassed = u2.unify(*overloadToUse, inferredTy);
|
const bool occursCheckPassed = u2.unify(*overloadToUse, inferredTy);
|
||||||
|
|
||||||
if (!u2.genericSubstitutions.empty() || !u2.genericPackSubstitutions.empty())
|
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)
|
if (!subst)
|
||||||
return {SolveResult::CodeTooComplex};
|
return {SolveResult::CodeTooComplex};
|
||||||
|
|
|
@ -107,134 +107,4 @@ void quantify(TypeId ty, TypeLevel level)
|
||||||
ftv->genericPacks.insert(ftv->genericPacks.end(), q.genericPacks.begin(), q.genericPacks.end());
|
ftv->genericPacks.insert(ftv->genericPacks.end(), q.genericPacks.begin(), q.genericPacks.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
struct PureQuantifier : Substitution
|
|
||||||
{
|
|
||||||
Scope* scope;
|
|
||||||
OrderedMap<TypeId, TypeId> insertedGenerics;
|
|
||||||
OrderedMap<TypePackId, TypePackId> insertedGenericPacks;
|
|
||||||
bool seenMutableType = false;
|
|
||||||
bool seenGenericType = false;
|
|
||||||
|
|
||||||
PureQuantifier(TypeArena* arena, Scope* scope)
|
|
||||||
: Substitution(TxnLog::empty(), arena)
|
|
||||||
, scope(scope)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isDirty(TypeId ty) override
|
|
||||||
{
|
|
||||||
LUAU_ASSERT(ty == follow(ty));
|
|
||||||
|
|
||||||
if (auto ftv = get<FreeType>(ty))
|
|
||||||
{
|
|
||||||
bool result = subsumes(scope, ftv->scope);
|
|
||||||
seenMutableType |= result;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
else if (auto ttv = get<TableType>(ty))
|
|
||||||
{
|
|
||||||
if (ttv->state == TableState::Free)
|
|
||||||
seenMutableType = true;
|
|
||||||
else if (ttv->state == TableState::Generic)
|
|
||||||
seenGenericType = true;
|
|
||||||
|
|
||||||
return (ttv->state == TableState::Unsealed || ttv->state == TableState::Free) && subsumes(scope, ttv->scope);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isDirty(TypePackId tp) override
|
|
||||||
{
|
|
||||||
if (auto ftp = get<FreeTypePack>(tp))
|
|
||||||
{
|
|
||||||
return subsumes(scope, ftp->scope);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
TypeId clean(TypeId ty) override
|
|
||||||
{
|
|
||||||
if (auto ftv = get<FreeType>(ty))
|
|
||||||
{
|
|
||||||
TypeId result = arena->addType(GenericType{scope});
|
|
||||||
insertedGenerics.push(ty, result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
else if (auto ttv = get<TableType>(ty))
|
|
||||||
{
|
|
||||||
TypeId result = arena->addType(TableType{});
|
|
||||||
TableType* resultTable = getMutable<TableType>(result);
|
|
||||||
LUAU_ASSERT(resultTable);
|
|
||||||
|
|
||||||
*resultTable = *ttv;
|
|
||||||
resultTable->level = TypeLevel{};
|
|
||||||
resultTable->scope = scope;
|
|
||||||
|
|
||||||
if (ttv->state == TableState::Free)
|
|
||||||
{
|
|
||||||
resultTable->state = TableState::Generic;
|
|
||||||
insertedGenerics.push(ty, result);
|
|
||||||
}
|
|
||||||
else if (ttv->state == TableState::Unsealed)
|
|
||||||
resultTable->state = TableState::Sealed;
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ty;
|
|
||||||
}
|
|
||||||
|
|
||||||
TypePackId clean(TypePackId tp) override
|
|
||||||
{
|
|
||||||
if (auto ftp = get<FreeTypePack>(tp))
|
|
||||||
{
|
|
||||||
TypePackId result = arena->addTypePack(TypePackVar{GenericTypePack{scope}});
|
|
||||||
insertedGenericPacks.push(tp, result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
return tp;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ignoreChildren(TypeId ty) override
|
|
||||||
{
|
|
||||||
if (get<ClassType>(ty))
|
|
||||||
return true;
|
|
||||||
|
|
||||||
return ty->persistent;
|
|
||||||
}
|
|
||||||
bool ignoreChildren(TypePackId ty) override
|
|
||||||
{
|
|
||||||
return ty->persistent;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
std::optional<QuantifierResult> quantify(TypeArena* arena, TypeId ty, Scope* scope)
|
|
||||||
{
|
|
||||||
PureQuantifier quantifier{arena, scope};
|
|
||||||
std::optional<TypeId> result = quantifier.substitute(ty);
|
|
||||||
if (!result)
|
|
||||||
return std::nullopt;
|
|
||||||
|
|
||||||
FunctionType* ftv = getMutable<FunctionType>(*result);
|
|
||||||
LUAU_ASSERT(ftv);
|
|
||||||
ftv->scope = scope;
|
|
||||||
|
|
||||||
for (auto k : quantifier.insertedGenerics.keys)
|
|
||||||
{
|
|
||||||
TypeId g = quantifier.insertedGenerics.pairings[k];
|
|
||||||
if (get<GenericType>(g))
|
|
||||||
ftv->generics.push_back(g);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto k : quantifier.insertedGenericPacks.keys)
|
|
||||||
ftv->genericPacks.push_back(quantifier.insertedGenericPacks.pairings[k]);
|
|
||||||
|
|
||||||
ftv->hasNoFreeOrGenericTypes = ftv->generics.empty() && ftv->genericPacks.empty() && !quantifier.seenGenericType && !quantifier.seenMutableType;
|
|
||||||
|
|
||||||
return std::optional<QuantifierResult>({*result, std::move(quantifier.insertedGenerics), std::move(quantifier.insertedGenericPacks)});
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Luau
|
} // namespace Luau
|
||||||
|
|
|
@ -54,7 +54,15 @@ RefinementId RefinementArena::proposition(const RefinementKey* key, TypeId discr
|
||||||
if (!key)
|
if (!key)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
return NotNull{allocator.allocate(Proposition{key, discriminantTy})};
|
return NotNull{allocator.allocate(Proposition{key, discriminantTy, false})};
|
||||||
|
}
|
||||||
|
|
||||||
|
RefinementId RefinementArena::implicitProposition(const RefinementKey* key, TypeId discriminantTy)
|
||||||
|
{
|
||||||
|
if (!key)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
return NotNull{allocator.allocate(Proposition{key, discriminantTy, true})};
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Luau
|
} // namespace Luau
|
||||||
|
|
|
@ -4,6 +4,8 @@
|
||||||
#include "Luau/Ast.h"
|
#include "Luau/Ast.h"
|
||||||
#include "Luau/Module.h"
|
#include "Luau/Module.h"
|
||||||
|
|
||||||
|
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
|
||||||
|
@ -65,7 +67,13 @@ struct RequireTracer : AstVisitor
|
||||||
return true;
|
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>())
|
if (AstExprLocal* expr = node->as<AstExprLocal>())
|
||||||
return locals[expr->local];
|
return locals[expr->local];
|
||||||
|
@ -78,6 +86,27 @@ struct RequireTracer : AstVisitor
|
||||||
else
|
else
|
||||||
return nullptr;
|
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()
|
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
|
// 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)
|
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);
|
work.push_back(dep);
|
||||||
|
}
|
||||||
|
|
||||||
// resolve all expressions to a module info
|
// resolve all expressions to a module info
|
||||||
for (size_t i = work.size(); i > 0; --i)
|
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
|
// when multiple expressions depend on the same one we push it to work queue multiple times
|
||||||
if (result.exprs.contains(expr))
|
if (result.exprs.contains(expr))
|
||||||
|
@ -105,19 +136,22 @@ struct RequireTracer : AstVisitor
|
||||||
|
|
||||||
std::optional<ModuleInfo> info;
|
std::optional<ModuleInfo> info;
|
||||||
|
|
||||||
if (AstExpr* dep = getDependent(expr))
|
if (AstNode* dep = getDependent(expr))
|
||||||
{
|
{
|
||||||
const ModuleInfo* context = result.exprs.find(dep);
|
const ModuleInfo* context = result.exprs.find(dep);
|
||||||
|
|
||||||
// locals just inherit their dependent context, no resolution required
|
if (context && expr->is<AstExprLocal>())
|
||||||
if (expr->is<AstExprLocal>())
|
info = *context; // locals just inherit their dependent context, no resolution required
|
||||||
info = context ? std::optional<ModuleInfo>(*context) : std::nullopt;
|
else if (context && (expr->is<AstExprGroup>() || expr->is<AstTypeGroup>()))
|
||||||
else
|
info = *context; // simple group nodes propagate their value
|
||||||
info = fileResolver->resolveModule(context, expr);
|
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)
|
if (info)
|
||||||
|
@ -150,7 +184,7 @@ struct RequireTracer : AstVisitor
|
||||||
ModuleName currentModuleName;
|
ModuleName currentModuleName;
|
||||||
|
|
||||||
DenseHashMap<AstLocal*, AstExpr*> locals;
|
DenseHashMap<AstLocal*, AstExpr*> locals;
|
||||||
std::vector<AstExpr*> work;
|
std::vector<AstNode*> work;
|
||||||
std::vector<AstExprCall*> requireCalls;
|
std::vector<AstExprCall*> requireCalls;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -84,6 +84,17 @@ std::optional<TypeId> Scope::lookupUnrefinedType(DefId def) const
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<TypeId> Scope::lookupRValueRefinementType(DefId def) const
|
||||||
|
{
|
||||||
|
for (const Scope* current = this; current; current = current->parent.get())
|
||||||
|
{
|
||||||
|
if (auto ty = current->rvalueRefinements.find(def))
|
||||||
|
return *ty;
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
std::optional<TypeId> Scope::lookup(DefId def) const
|
std::optional<TypeId> Scope::lookup(DefId def) const
|
||||||
{
|
{
|
||||||
for (const Scope* current = this; current; current = current->parent.get())
|
for (const Scope* current = this; current; current = current->parent.get())
|
||||||
|
@ -181,6 +192,29 @@ std::optional<Binding> Scope::linearSearchForBinding(const std::string& name, bo
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<std::pair<Symbol, Binding>> Scope::linearSearchForBindingPair(const std::string& name, bool traverseScopeChain) const
|
||||||
|
{
|
||||||
|
const Scope* scope = this;
|
||||||
|
|
||||||
|
while (scope)
|
||||||
|
{
|
||||||
|
for (auto& [n, binding] : scope->bindings)
|
||||||
|
{
|
||||||
|
if (n.local && n.local->name == name.c_str())
|
||||||
|
return {{n, binding}};
|
||||||
|
else if (n.global.value && n.global == name.c_str())
|
||||||
|
return {{n, binding}};
|
||||||
|
}
|
||||||
|
|
||||||
|
scope = scope->parent.get();
|
||||||
|
|
||||||
|
if (!traverseScopeChain)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
// Updates the `this` scope with the assignments from the `childScope` including ones that doesn't exist in `this`.
|
// Updates the `this` scope with the assignments from the `childScope` including ones that doesn't exist in `this`.
|
||||||
void Scope::inheritAssignments(const ScopePtr& childScope)
|
void Scope::inheritAssignments(const ScopePtr& childScope)
|
||||||
{
|
{
|
||||||
|
@ -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)
|
bool subsumesStrict(Scope* left, Scope* right)
|
||||||
{
|
{
|
||||||
while (right)
|
while (right)
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
#include "Luau/DenseHash.h"
|
#include "Luau/DenseHash.h"
|
||||||
#include "Luau/RecursionCounter.h"
|
#include "Luau/RecursionCounter.h"
|
||||||
#include "Luau/Set.h"
|
#include "Luau/Set.h"
|
||||||
|
#include "Luau/Type.h"
|
||||||
#include "Luau/TypeArena.h"
|
#include "Luau/TypeArena.h"
|
||||||
#include "Luau/TypePairHash.h"
|
#include "Luau/TypePairHash.h"
|
||||||
#include "Luau/TypeUtils.h"
|
#include "Luau/TypeUtils.h"
|
||||||
|
@ -14,8 +15,10 @@
|
||||||
|
|
||||||
LUAU_FASTINT(LuauTypeReductionRecursionLimit)
|
LUAU_FASTINT(LuauTypeReductionRecursionLimit)
|
||||||
LUAU_FASTFLAG(LuauSolverV2)
|
LUAU_FASTFLAG(LuauSolverV2)
|
||||||
LUAU_DYNAMIC_FASTINTVARIABLE(LuauSimplificationComplexityLimit, 8);
|
LUAU_DYNAMIC_FASTINTVARIABLE(LuauSimplificationComplexityLimit, 8)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauFlagBasicIntersectFollows);
|
LUAU_FASTFLAGVARIABLE(LuauSimplificationRecheckAssumption)
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauOptimizeFalsyAndTruthyIntersect)
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauSimplificationTableExternType)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
@ -31,25 +34,27 @@ struct TypeSimplifier
|
||||||
|
|
||||||
int recursionDepth = 0;
|
int recursionDepth = 0;
|
||||||
|
|
||||||
TypeId mkNegation(TypeId ty);
|
TypeId mkNegation(TypeId ty) const;
|
||||||
|
|
||||||
TypeId intersectFromParts(std::set<TypeId> parts);
|
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 intersectUnions(TypeId left, TypeId right);
|
||||||
TypeId intersectNegatedUnion(TypeId unionTy, TypeId right);
|
TypeId intersectNegatedUnion(TypeId left, TypeId right);
|
||||||
|
|
||||||
TypeId intersectTypeWithNegation(TypeId a, TypeId b);
|
TypeId intersectTypeWithNegation(TypeId left, TypeId right);
|
||||||
TypeId intersectNegations(TypeId a, TypeId b);
|
TypeId intersectNegations(TypeId left, TypeId right);
|
||||||
|
|
||||||
TypeId intersectIntersectionWithType(TypeId left, TypeId right);
|
TypeId intersectIntersectionWithType(TypeId left, TypeId right);
|
||||||
|
|
||||||
// Attempt to intersect the two types. Does not recurse. Does not handle
|
// Attempt to intersect the two types. Does not recurse. Does not handle
|
||||||
// unions, intersections, or negations.
|
// unions, intersections, or negations.
|
||||||
std::optional<TypeId> basicIntersect(TypeId left, TypeId right);
|
std::optional<TypeId> basicIntersect(TypeId left, TypeId right);
|
||||||
|
std::optional<TypeId> basicIntersectWithTruthy(TypeId target) const;
|
||||||
|
std::optional<TypeId> basicIntersectWithFalsy(TypeId target) const;
|
||||||
|
|
||||||
TypeId intersect(TypeId ty, TypeId discriminant);
|
TypeId intersect(TypeId left, TypeId right);
|
||||||
TypeId union_(TypeId ty, TypeId discriminant);
|
TypeId union_(TypeId left, TypeId right);
|
||||||
|
|
||||||
TypeId simplify(TypeId ty);
|
TypeId simplify(TypeId ty);
|
||||||
TypeId simplify(TypeId ty, DenseHashSet<TypeId>& seen);
|
TypeId simplify(TypeId ty, DenseHashSet<TypeId>& seen);
|
||||||
|
@ -313,12 +318,14 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
|
||||||
{
|
{
|
||||||
if (get<AnyType>(right))
|
if (get<AnyType>(right))
|
||||||
return Relation::Subset;
|
return Relation::Subset;
|
||||||
else if (get<UnknownType>(right))
|
|
||||||
|
if (get<UnknownType>(right))
|
||||||
return Relation::Coincident;
|
return Relation::Coincident;
|
||||||
else if (get<ErrorType>(right))
|
|
||||||
|
if (get<ErrorType>(right))
|
||||||
return Relation::Disjoint;
|
return Relation::Disjoint;
|
||||||
else
|
|
||||||
return Relation::Superset;
|
return Relation::Superset;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (get<UnknownType>(right))
|
if (get<UnknownType>(right))
|
||||||
|
@ -328,8 +335,8 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
|
||||||
{
|
{
|
||||||
if (get<AnyType>(right))
|
if (get<AnyType>(right))
|
||||||
return Relation::Coincident;
|
return Relation::Coincident;
|
||||||
else
|
|
||||||
return Relation::Superset;
|
return Relation::Superset;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (get<AnyType>(right))
|
if (get<AnyType>(right))
|
||||||
|
@ -353,7 +360,7 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
|
||||||
// * FunctionType
|
// * FunctionType
|
||||||
// * TableType
|
// * TableType
|
||||||
// * MetatableType
|
// * MetatableType
|
||||||
// * ClassType
|
// * ExternType
|
||||||
// * UnionType
|
// * UnionType
|
||||||
// * IntersectionType
|
// * IntersectionType
|
||||||
// * NegationType
|
// * NegationType
|
||||||
|
@ -361,26 +368,33 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
|
||||||
if (isTypeVariable(left) || isTypeVariable(right))
|
if (isTypeVariable(left) || isTypeVariable(right))
|
||||||
return Relation::Intersects;
|
return Relation::Intersects;
|
||||||
|
|
||||||
|
if (FFlag::LuauSimplificationTableExternType)
|
||||||
|
{
|
||||||
|
// if either type is a type function, we cannot know if they'll be related.
|
||||||
|
if (get<TypeFunctionInstanceType>(left) || get<TypeFunctionInstanceType>(right))
|
||||||
|
return Relation::Intersects;
|
||||||
|
}
|
||||||
|
|
||||||
if (get<ErrorType>(left))
|
if (get<ErrorType>(left))
|
||||||
{
|
{
|
||||||
if (get<ErrorType>(right))
|
if (get<ErrorType>(right))
|
||||||
return Relation::Coincident;
|
return Relation::Coincident;
|
||||||
else if (get<AnyType>(right))
|
else if (get<AnyType>(right))
|
||||||
return Relation::Subset;
|
return Relation::Subset;
|
||||||
else
|
|
||||||
return Relation::Disjoint;
|
return Relation::Disjoint;
|
||||||
}
|
}
|
||||||
if (get<ErrorType>(right))
|
else if (get<ErrorType>(right))
|
||||||
return flip(relate(right, left, seen));
|
return flip(relate(right, left, seen));
|
||||||
|
|
||||||
if (get<NeverType>(left))
|
if (get<NeverType>(left))
|
||||||
{
|
{
|
||||||
if (get<NeverType>(right))
|
if (get<NeverType>(right))
|
||||||
return Relation::Coincident;
|
return Relation::Coincident;
|
||||||
else
|
|
||||||
return Relation::Subset;
|
return Relation::Subset;
|
||||||
}
|
}
|
||||||
if (get<NeverType>(right))
|
else if (get<NeverType>(right))
|
||||||
return flip(relate(right, left, seen));
|
return flip(relate(right, left, seen));
|
||||||
|
|
||||||
if (auto ut = get<IntersectionType>(left))
|
if (auto ut = get<IntersectionType>(left))
|
||||||
|
@ -444,52 +458,54 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
|
||||||
{
|
{
|
||||||
if (lp->type == rp->type)
|
if (lp->type == rp->type)
|
||||||
return Relation::Coincident;
|
return Relation::Coincident;
|
||||||
else
|
|
||||||
return Relation::Disjoint;
|
return Relation::Disjoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auto rs = get<SingletonType>(right))
|
if (auto rs = get<SingletonType>(right))
|
||||||
{
|
{
|
||||||
if (lp->type == PrimitiveType::String && rs->variant.get_if<StringSingleton>())
|
if (lp->type == PrimitiveType::String && rs->variant.get_if<StringSingleton>())
|
||||||
return Relation::Superset;
|
return Relation::Superset;
|
||||||
else if (lp->type == PrimitiveType::Boolean && rs->variant.get_if<BooleanSingleton>())
|
|
||||||
|
if (lp->type == PrimitiveType::Boolean && rs->variant.get_if<BooleanSingleton>())
|
||||||
return Relation::Superset;
|
return Relation::Superset;
|
||||||
else
|
|
||||||
return Relation::Disjoint;
|
return Relation::Disjoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lp->type == PrimitiveType::Function)
|
if (lp->type == PrimitiveType::Function)
|
||||||
{
|
{
|
||||||
if (get<FunctionType>(right))
|
if (get<FunctionType>(right))
|
||||||
return Relation::Superset;
|
return Relation::Superset;
|
||||||
else
|
|
||||||
return Relation::Disjoint;
|
return Relation::Disjoint;
|
||||||
}
|
}
|
||||||
if (lp->type == PrimitiveType::Table)
|
if (lp->type == PrimitiveType::Table)
|
||||||
{
|
{
|
||||||
if (get<TableType>(right))
|
if (get<TableType>(right))
|
||||||
return Relation::Superset;
|
return Relation::Superset;
|
||||||
else
|
|
||||||
return Relation::Disjoint;
|
return Relation::Disjoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (get<FunctionType>(right) || get<TableType>(right) || get<MetatableType>(right) || get<ClassType>(right))
|
if (get<FunctionType>(right) || get<TableType>(right) || get<MetatableType>(right) || get<ExternType>(right))
|
||||||
return Relation::Disjoint;
|
return Relation::Disjoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auto ls = get<SingletonType>(left))
|
if (auto ls = get<SingletonType>(left))
|
||||||
{
|
{
|
||||||
if (get<FunctionType>(right) || get<TableType>(right) || get<MetatableType>(right) || get<ClassType>(right))
|
if (get<FunctionType>(right) || get<TableType>(right) || get<MetatableType>(right) || get<ExternType>(right))
|
||||||
return Relation::Disjoint;
|
return Relation::Disjoint;
|
||||||
|
|
||||||
if (get<PrimitiveType>(right))
|
if (get<PrimitiveType>(right))
|
||||||
return flip(relate(right, left, seen));
|
return flip(relate(right, left, seen));
|
||||||
|
|
||||||
if (auto rs = get<SingletonType>(right))
|
if (auto rs = get<SingletonType>(right))
|
||||||
{
|
{
|
||||||
if (ls->variant == rs->variant)
|
if (ls->variant == rs->variant)
|
||||||
return Relation::Coincident;
|
return Relation::Coincident;
|
||||||
else
|
|
||||||
return Relation::Disjoint;
|
return Relation::Disjoint;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -499,11 +515,11 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
|
||||||
{
|
{
|
||||||
if (rp->type == PrimitiveType::Function)
|
if (rp->type == PrimitiveType::Function)
|
||||||
return Relation::Subset;
|
return Relation::Subset;
|
||||||
else
|
|
||||||
return Relation::Disjoint;
|
return Relation::Disjoint;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
return Relation::Intersects;
|
return Relation::Intersects;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auto lt = get<TableType>(left))
|
if (auto lt = get<TableType>(left))
|
||||||
|
@ -512,10 +528,11 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
|
||||||
{
|
{
|
||||||
if (rp->type == PrimitiveType::Table)
|
if (rp->type == PrimitiveType::Table)
|
||||||
return Relation::Subset;
|
return Relation::Subset;
|
||||||
else
|
|
||||||
return Relation::Disjoint;
|
return Relation::Disjoint;
|
||||||
}
|
}
|
||||||
else if (auto rt = get<TableType>(right))
|
|
||||||
|
if (auto rt = get<TableType>(right))
|
||||||
{
|
{
|
||||||
// TODO PROBABLY indexers and metatables.
|
// TODO PROBABLY indexers and metatables.
|
||||||
if (1 == rt->props.size())
|
if (1 == rt->props.size())
|
||||||
|
@ -535,29 +552,58 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
|
||||||
*/
|
*/
|
||||||
if (lt->props.size() > 1 && r == Relation::Superset)
|
if (lt->props.size() > 1 && r == Relation::Superset)
|
||||||
return Relation::Intersects;
|
return Relation::Intersects;
|
||||||
else
|
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
else if (1 == lt->props.size())
|
|
||||||
|
if (1 == lt->props.size())
|
||||||
return flip(relate(right, left, seen));
|
return flip(relate(right, left, seen));
|
||||||
else
|
|
||||||
return Relation::Intersects;
|
return Relation::Intersects;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (FFlag::LuauSimplificationTableExternType)
|
||||||
|
{
|
||||||
|
if (auto re = get<ExternType>(right))
|
||||||
|
{
|
||||||
|
Relation overall = Relation::Coincident;
|
||||||
|
|
||||||
|
for (auto& [name, prop] : lt->props)
|
||||||
|
{
|
||||||
|
if (auto propInExternType = re->props.find(name); propInExternType != re->props.end())
|
||||||
|
{
|
||||||
|
Relation propRel = relate(prop.type(), propInExternType->second.type());
|
||||||
|
|
||||||
|
if (propRel == Relation::Disjoint)
|
||||||
|
return Relation::Disjoint;
|
||||||
|
|
||||||
|
if (propRel == Relation::Coincident)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
overall = Relation::Intersects;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return overall;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO metatables
|
// TODO metatables
|
||||||
|
|
||||||
return Relation::Disjoint;
|
return Relation::Disjoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auto ct = get<ClassType>(left))
|
if (auto ct = get<ExternType>(left))
|
||||||
{
|
{
|
||||||
if (auto rct = get<ClassType>(right))
|
if (auto rct = get<ExternType>(right))
|
||||||
{
|
{
|
||||||
if (isSubclass(ct, rct))
|
if (isSubclass(ct, rct))
|
||||||
return Relation::Subset;
|
return Relation::Subset;
|
||||||
else if (isSubclass(rct, ct))
|
|
||||||
|
if (isSubclass(rct, ct))
|
||||||
return Relation::Superset;
|
return Relation::Superset;
|
||||||
else
|
|
||||||
return Relation::Disjoint;
|
return Relation::Disjoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Relation::Disjoint;
|
return Relation::Disjoint;
|
||||||
|
@ -573,7 +619,7 @@ Relation relate(TypeId left, TypeId right)
|
||||||
return relate(left, right, seen);
|
return relate(left, right, seen);
|
||||||
}
|
}
|
||||||
|
|
||||||
TypeId TypeSimplifier::mkNegation(TypeId ty)
|
TypeId TypeSimplifier::mkNegation(TypeId ty) const
|
||||||
{
|
{
|
||||||
TypeId result = nullptr;
|
TypeId result = nullptr;
|
||||||
|
|
||||||
|
@ -707,7 +753,9 @@ TypeId TypeSimplifier::intersectUnionWithType(TypeId left, TypeId right)
|
||||||
bool changed = false;
|
bool changed = false;
|
||||||
std::set<TypeId> newParts;
|
std::set<TypeId> newParts;
|
||||||
|
|
||||||
if (leftUnion->options.size() > (size_t)DFInt::LuauSimplificationComplexityLimit)
|
size_t maxSize = DFInt::LuauSimplificationComplexityLimit;
|
||||||
|
|
||||||
|
if (leftUnion->options.size() > maxSize)
|
||||||
return arena->addType(IntersectionType{{left, right}});
|
return arena->addType(IntersectionType{{left, right}});
|
||||||
|
|
||||||
for (TypeId part : leftUnion)
|
for (TypeId part : leftUnion)
|
||||||
|
@ -722,6 +770,13 @@ TypeId TypeSimplifier::intersectUnionWithType(TypeId left, TypeId right)
|
||||||
}
|
}
|
||||||
|
|
||||||
newParts.insert(simplified);
|
newParts.insert(simplified);
|
||||||
|
|
||||||
|
if (FFlag::LuauSimplificationRecheckAssumption)
|
||||||
|
{
|
||||||
|
// Initial combination size check could not predict nested union iteration
|
||||||
|
if (newParts.size() > maxSize)
|
||||||
|
return arena->addType(IntersectionType{{left, right}});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!changed)
|
if (!changed)
|
||||||
|
@ -762,6 +817,13 @@ TypeId TypeSimplifier::intersectUnions(TypeId left, TypeId right)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
newParts.insert(simplified);
|
newParts.insert(simplified);
|
||||||
|
|
||||||
|
if (FFlag::LuauSimplificationRecheckAssumption)
|
||||||
|
{
|
||||||
|
// Initial combination size check could not predict nested union iteration
|
||||||
|
if (newParts.size() > maxSize)
|
||||||
|
return arena->addType(IntersectionType{{left, right}});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -840,6 +902,78 @@ TypeId TypeSimplifier::intersectNegatedUnion(TypeId left, TypeId right)
|
||||||
return intersectFromParts(std::move(newParts));
|
return intersectFromParts(std::move(newParts));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<TypeId> TypeSimplifier::basicIntersectWithTruthy(TypeId target) const
|
||||||
|
{
|
||||||
|
target = follow(target);
|
||||||
|
|
||||||
|
if (is<UnknownType>(target))
|
||||||
|
return builtinTypes->truthyType;
|
||||||
|
|
||||||
|
if (is<AnyType>(target))
|
||||||
|
// any = *error-type* | unknown, so truthy & any = *error-type* | truthy
|
||||||
|
return arena->addType(UnionType{{builtinTypes->truthyType, builtinTypes->errorType}});
|
||||||
|
|
||||||
|
if (is<NeverType, ErrorType>(target))
|
||||||
|
return target;
|
||||||
|
|
||||||
|
if (is<FunctionType, TableType, MetatableType, ExternType>(target))
|
||||||
|
return target;
|
||||||
|
|
||||||
|
if (auto pt = get<PrimitiveType>(target))
|
||||||
|
{
|
||||||
|
switch (pt->type)
|
||||||
|
{
|
||||||
|
case PrimitiveType::NilType:
|
||||||
|
return builtinTypes->neverType;
|
||||||
|
case PrimitiveType::Boolean:
|
||||||
|
return builtinTypes->trueType;
|
||||||
|
default:
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto st = get<SingletonType>(target))
|
||||||
|
return st->variant == BooleanSingleton{false} ? builtinTypes->neverType : target;
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<TypeId> TypeSimplifier::basicIntersectWithFalsy(TypeId target) const
|
||||||
|
{
|
||||||
|
target = follow(target);
|
||||||
|
|
||||||
|
if (is<NeverType, ErrorType>(target))
|
||||||
|
return target;
|
||||||
|
|
||||||
|
if (is<AnyType>(target))
|
||||||
|
// any = *error-type* | unknown, so falsy & any = *error-type* | falsy
|
||||||
|
return arena->addType(UnionType{{builtinTypes->falsyType, builtinTypes->errorType}});
|
||||||
|
|
||||||
|
if (is<UnknownType>(target))
|
||||||
|
return builtinTypes->falsyType;
|
||||||
|
|
||||||
|
if (is<FunctionType, TableType, MetatableType, ExternType>(target))
|
||||||
|
return builtinTypes->neverType;
|
||||||
|
|
||||||
|
if (auto pt = get<PrimitiveType>(target))
|
||||||
|
{
|
||||||
|
switch (pt->type)
|
||||||
|
{
|
||||||
|
case PrimitiveType::NilType:
|
||||||
|
return builtinTypes->nilType;
|
||||||
|
case PrimitiveType::Boolean:
|
||||||
|
return builtinTypes->falseType;
|
||||||
|
default:
|
||||||
|
return builtinTypes->neverType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto st = get<SingletonType>(target))
|
||||||
|
return st->variant == BooleanSingleton{false} ? builtinTypes->falseType : builtinTypes->neverType;
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
TypeId TypeSimplifier::intersectTypeWithNegation(TypeId left, TypeId right)
|
TypeId TypeSimplifier::intersectTypeWithNegation(TypeId left, TypeId right)
|
||||||
{
|
{
|
||||||
const NegationType* leftNegation = get<NegationType>(left);
|
const NegationType* leftNegation = get<NegationType>(left);
|
||||||
|
@ -1066,11 +1200,8 @@ TypeId TypeSimplifier::intersectIntersectionWithType(TypeId left, TypeId right)
|
||||||
|
|
||||||
std::optional<TypeId> TypeSimplifier::basicIntersect(TypeId left, TypeId right)
|
std::optional<TypeId> TypeSimplifier::basicIntersect(TypeId left, TypeId right)
|
||||||
{
|
{
|
||||||
if (FFlag::LuauFlagBasicIntersectFollows)
|
left = follow(left);
|
||||||
{
|
right = follow(right);
|
||||||
left = follow(left);
|
|
||||||
right = follow(right);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (get<AnyType>(left) && get<ErrorType>(right))
|
if (get<AnyType>(left) && get<ErrorType>(right))
|
||||||
return right;
|
return right;
|
||||||
|
@ -1179,6 +1310,25 @@ std::optional<TypeId> TypeSimplifier::basicIntersect(TypeId left, TypeId right)
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (FFlag::LuauOptimizeFalsyAndTruthyIntersect)
|
||||||
|
{
|
||||||
|
if (isTruthyType(left))
|
||||||
|
if (auto res = basicIntersectWithTruthy(right))
|
||||||
|
return res;
|
||||||
|
|
||||||
|
if (isTruthyType(right))
|
||||||
|
if (auto res = basicIntersectWithTruthy(left))
|
||||||
|
return res;
|
||||||
|
|
||||||
|
if (isFalsyType(left))
|
||||||
|
if (auto res = basicIntersectWithFalsy(right))
|
||||||
|
return res;
|
||||||
|
|
||||||
|
if (isFalsyType(right))
|
||||||
|
if (auto res = basicIntersectWithFalsy(left))
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
Relation relation = relate(left, right);
|
Relation relation = relate(left, right);
|
||||||
if (left == right || Relation::Coincident == relation)
|
if (left == right || Relation::Coincident == relation)
|
||||||
return left;
|
return left;
|
||||||
|
@ -1411,8 +1561,6 @@ TypeId TypeSimplifier::simplify(TypeId ty, DenseHashSet<TypeId>& seen)
|
||||||
|
|
||||||
SimplifyResult simplifyIntersection(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, TypeId left, TypeId right)
|
SimplifyResult simplifyIntersection(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, TypeId left, TypeId right)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(FFlag::LuauSolverV2);
|
|
||||||
|
|
||||||
TypeSimplifier s{builtinTypes, arena};
|
TypeSimplifier s{builtinTypes, arena};
|
||||||
|
|
||||||
// fprintf(stderr, "Intersect %s and %s ...\n", toString(left).c_str(), toString(right).c_str());
|
// 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)
|
SimplifyResult simplifyIntersection(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, std::set<TypeId> parts)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(FFlag::LuauSolverV2);
|
|
||||||
|
|
||||||
TypeSimplifier s{builtinTypes, arena};
|
TypeSimplifier s{builtinTypes, arena};
|
||||||
|
|
||||||
TypeId res = s.intersectFromParts(std::move(parts));
|
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)
|
SimplifyResult simplifyUnion(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, TypeId left, TypeId right)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(FFlag::LuauSolverV2);
|
|
||||||
|
|
||||||
TypeSimplifier s{builtinTypes, arena};
|
TypeSimplifier s{builtinTypes, arena};
|
||||||
|
|
||||||
TypeId res = s.union_(left, right);
|
TypeId res = s.union_(left, right);
|
||||||
|
|
|
@ -2,22 +2,23 @@
|
||||||
#include "Luau/Substitution.h"
|
#include "Luau/Substitution.h"
|
||||||
|
|
||||||
#include "Luau/Common.h"
|
#include "Luau/Common.h"
|
||||||
#include "Luau/Clone.h"
|
|
||||||
#include "Luau/TxnLog.h"
|
#include "Luau/TxnLog.h"
|
||||||
|
#include "Luau/Type.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <stdexcept>
|
|
||||||
|
|
||||||
LUAU_FASTINTVARIABLE(LuauTarjanChildLimit, 10000)
|
LUAU_FASTINTVARIABLE(LuauTarjanChildLimit, 10000)
|
||||||
LUAU_FASTFLAG(LuauSolverV2);
|
LUAU_FASTFLAG(LuauSolverV2)
|
||||||
LUAU_FASTINTVARIABLE(LuauTarjanPreallocationSize, 256);
|
LUAU_FASTINTVARIABLE(LuauTarjanPreallocationSize, 256)
|
||||||
|
LUAU_FASTFLAG(LuauSyntheticErrors)
|
||||||
|
LUAU_FASTFLAG(LuauDeprecatedAttribute)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
|
||||||
static TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log, bool alwaysClone)
|
static TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log)
|
||||||
{
|
{
|
||||||
auto go = [ty, &dest, alwaysClone](auto&& a)
|
auto go = [ty, &dest](auto&& a)
|
||||||
{
|
{
|
||||||
using T = std::decay_t<decltype(a)>;
|
using T = std::decay_t<decltype(a)>;
|
||||||
|
|
||||||
|
@ -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>)
|
else if constexpr (std::is_same_v<T, ErrorType>)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(ty->persistent);
|
if (FFlag::LuauSyntheticErrors)
|
||||||
return ty;
|
{
|
||||||
|
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>)
|
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);
|
return dest.addType(a);
|
||||||
else if constexpr (std::is_same_v<T, FunctionType>)
|
else if constexpr (std::is_same_v<T, FunctionType>)
|
||||||
{
|
{
|
||||||
FunctionType clone = FunctionType{a.level, a.scope, a.argTypes, a.retTypes, a.definition, a.hasSelf};
|
FunctionType clone = FunctionType{a.level, a.argTypes, a.retTypes, a.definition, a.hasSelf};
|
||||||
clone.generics = a.generics;
|
clone.generics = a.generics;
|
||||||
clone.genericPacks = a.genericPacks;
|
clone.genericPacks = a.genericPacks;
|
||||||
clone.magicFunction = a.magicFunction;
|
clone.magic = a.magic;
|
||||||
clone.dcrMagicFunction = a.dcrMagicFunction;
|
|
||||||
clone.dcrMagicRefinement = a.dcrMagicRefinement;
|
|
||||||
clone.tags = a.tags;
|
clone.tags = a.tags;
|
||||||
clone.argNames = a.argNames;
|
clone.argNames = a.argNames;
|
||||||
clone.isCheckedFunction = a.isCheckedFunction;
|
clone.isCheckedFunction = a.isCheckedFunction;
|
||||||
|
if (FFlag::LuauDeprecatedAttribute)
|
||||||
|
clone.isDeprecatedFunction = a.isDeprecatedFunction;
|
||||||
return dest.addType(std::move(clone));
|
return dest.addType(std::move(clone));
|
||||||
}
|
}
|
||||||
else if constexpr (std::is_same_v<T, TableType>)
|
else if constexpr (std::is_same_v<T, TableType>)
|
||||||
|
@ -118,15 +136,10 @@ static TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log, bool a
|
||||||
clone.parts = a.parts;
|
clone.parts = a.parts;
|
||||||
return dest.addType(std::move(clone));
|
return dest.addType(std::move(clone));
|
||||||
}
|
}
|
||||||
else if constexpr (std::is_same_v<T, ClassType>)
|
else if constexpr (std::is_same_v<T, ExternType>)
|
||||||
{
|
{
|
||||||
if (alwaysClone)
|
ExternType clone{a.name, a.props, a.parent, a.metatable, a.tags, a.userData, a.definitionModuleName, a.definitionLocation, a.indexer};
|
||||||
{
|
return dest.addType(std::move(clone));
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
else if constexpr (std::is_same_v<T, NegationType>)
|
else if constexpr (std::is_same_v<T, NegationType>)
|
||||||
return dest.addType(NegationType{a.ty});
|
return dest.addType(NegationType{a.ty});
|
||||||
|
@ -239,21 +252,21 @@ void Tarjan::visitChildren(TypeId ty, int index)
|
||||||
for (TypePackId a : tfit->packArguments)
|
for (TypePackId a : tfit->packArguments)
|
||||||
visitChild(a);
|
visitChild(a);
|
||||||
}
|
}
|
||||||
else if (const ClassType* ctv = get<ClassType>(ty))
|
else if (const ExternType* etv = get<ExternType>(ty))
|
||||||
{
|
{
|
||||||
for (const auto& [name, prop] : ctv->props)
|
for (const auto& [name, prop] : etv->props)
|
||||||
visitChild(prop.type());
|
visitChild(prop.type());
|
||||||
|
|
||||||
if (ctv->parent)
|
if (etv->parent)
|
||||||
visitChild(*ctv->parent);
|
visitChild(*etv->parent);
|
||||||
|
|
||||||
if (ctv->metatable)
|
if (etv->metatable)
|
||||||
visitChild(*ctv->metatable);
|
visitChild(*etv->metatable);
|
||||||
|
|
||||||
if (ctv->indexer)
|
if (etv->indexer)
|
||||||
{
|
{
|
||||||
visitChild(ctv->indexer->indexType);
|
visitChild(etv->indexer->indexType);
|
||||||
visitChild(ctv->indexer->indexResultType);
|
visitChild(etv->indexer->indexResultType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (const NegationType* ntv = get<NegationType>(ty))
|
else if (const NegationType* ntv = get<NegationType>(ty))
|
||||||
|
@ -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)
|
TarjanResult Tarjan::findDirty(TypeId ty)
|
||||||
{
|
{
|
||||||
return visitRoot(ty);
|
return visitRoot(ty);
|
||||||
|
@ -537,6 +571,11 @@ TarjanResult Tarjan::findDirty(TypePackId tp)
|
||||||
return visitRoot(tp);
|
return visitRoot(tp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Substitution::Substitution(TypeArena* arena)
|
||||||
|
: Substitution(TxnLog::empty(), arena)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
Substitution::Substitution(const TxnLog* log_, TypeArena* arena)
|
Substitution::Substitution(const TxnLog* log_, TypeArena* arena)
|
||||||
: arena(arena)
|
: arena(arena)
|
||||||
{
|
{
|
||||||
|
@ -637,7 +676,7 @@ void Substitution::resetState(const TxnLog* log, TypeArena* arena)
|
||||||
|
|
||||||
TypeId Substitution::clone(TypeId ty)
|
TypeId Substitution::clone(TypeId ty)
|
||||||
{
|
{
|
||||||
return shallowClone(ty, *arena, log, /* alwaysClone */ true);
|
return shallowClone(ty, *arena, log);
|
||||||
}
|
}
|
||||||
|
|
||||||
TypePackId Substitution::clone(TypePackId tp)
|
TypePackId Substitution::clone(TypePackId tp)
|
||||||
|
@ -799,21 +838,21 @@ void Substitution::replaceChildren(TypeId ty)
|
||||||
for (TypePackId& a : tfit->packArguments)
|
for (TypePackId& a : tfit->packArguments)
|
||||||
a = replace(a);
|
a = replace(a);
|
||||||
}
|
}
|
||||||
else if (ClassType* ctv = getMutable<ClassType>(ty))
|
else if (ExternType* etv = getMutable<ExternType>(ty))
|
||||||
{
|
{
|
||||||
for (auto& [name, prop] : ctv->props)
|
for (auto& [name, prop] : etv->props)
|
||||||
prop.setType(replace(prop.type()));
|
prop.setType(replace(prop.type()));
|
||||||
|
|
||||||
if (ctv->parent)
|
if (etv->parent)
|
||||||
ctv->parent = replace(*ctv->parent);
|
etv->parent = replace(*etv->parent);
|
||||||
|
|
||||||
if (ctv->metatable)
|
if (etv->metatable)
|
||||||
ctv->metatable = replace(*ctv->metatable);
|
etv->metatable = replace(*etv->metatable);
|
||||||
|
|
||||||
if (ctv->indexer)
|
if (etv->indexer)
|
||||||
{
|
{
|
||||||
ctv->indexer->indexType = replace(ctv->indexer->indexType);
|
etv->indexer->indexType = replace(etv->indexer->indexType);
|
||||||
ctv->indexer->indexResultType = replace(ctv->indexer->indexResultType);
|
etv->indexer->indexResultType = replace(etv->indexer->indexResultType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (NegationType* ntv = getMutable<NegationType>(ty))
|
else if (NegationType* ntv = getMutable<NegationType>(ty))
|
||||||
|
@ -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
|
} // namespace Luau
|
||||||
|
|
|
@ -7,13 +7,11 @@
|
||||||
#include "Luau/Normalize.h"
|
#include "Luau/Normalize.h"
|
||||||
#include "Luau/RecursionCounter.h"
|
#include "Luau/RecursionCounter.h"
|
||||||
#include "Luau/Scope.h"
|
#include "Luau/Scope.h"
|
||||||
#include "Luau/StringUtils.h"
|
|
||||||
#include "Luau/Substitution.h"
|
#include "Luau/Substitution.h"
|
||||||
#include "Luau/ToString.h"
|
#include "Luau/ToString.h"
|
||||||
#include "Luau/TxnLog.h"
|
#include "Luau/TxnLog.h"
|
||||||
#include "Luau/Type.h"
|
#include "Luau/Type.h"
|
||||||
#include "Luau/TypeArena.h"
|
#include "Luau/TypeArena.h"
|
||||||
#include "Luau/TypeCheckLimits.h"
|
|
||||||
#include "Luau/TypeFunction.h"
|
#include "Luau/TypeFunction.h"
|
||||||
#include "Luau/TypePack.h"
|
#include "Luau/TypePack.h"
|
||||||
#include "Luau/TypePath.h"
|
#include "Luau/TypePath.h"
|
||||||
|
@ -22,8 +20,8 @@
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
LUAU_FASTFLAGVARIABLE(DebugLuauSubtypingCheckPathValidity)
|
LUAU_FASTFLAGVARIABLE(DebugLuauSubtypingCheckPathValidity)
|
||||||
LUAU_DYNAMIC_FASTINT(LuauTypeSolverRelease)
|
LUAU_FASTINTVARIABLE(LuauSubtypingReasoningLimit, 100)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauRetrySubtypingWithoutHiddenPack)
|
LUAU_FASTFLAGVARIABLE(LuauSubtypingEnableReasoningLimit)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
@ -33,7 +31,7 @@ struct VarianceFlipper
|
||||||
Subtyping::Variance* variance;
|
Subtyping::Variance* variance;
|
||||||
Subtyping::Variance oldValue;
|
Subtyping::Variance oldValue;
|
||||||
|
|
||||||
VarianceFlipper(Subtyping::Variance* v)
|
explicit VarianceFlipper(Subtyping::Variance* v)
|
||||||
: variance(v)
|
: variance(v)
|
||||||
, oldValue(*v)
|
, oldValue(*v)
|
||||||
{
|
{
|
||||||
|
@ -101,6 +99,9 @@ static SubtypingReasonings mergeReasonings(const SubtypingReasonings& a, const S
|
||||||
else
|
else
|
||||||
result.insert(r);
|
result.insert(r);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (FFlag::LuauSubtypingEnableReasoningLimit && result.size() >= size_t(FInt::LuauSubtypingReasoningLimit))
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const SubtypingReasoning& r : b)
|
for (const SubtypingReasoning& r : b)
|
||||||
|
@ -117,6 +118,9 @@ static SubtypingReasonings mergeReasonings(const SubtypingReasonings& a, const S
|
||||||
else
|
else
|
||||||
result.insert(r);
|
result.insert(r);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (FFlag::LuauSubtypingEnableReasoningLimit && result.size() >= size_t(FInt::LuauSubtypingReasoningLimit))
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
@ -309,7 +313,7 @@ struct ApplyMappedGenerics : Substitution
|
||||||
|
|
||||||
bool ignoreChildren(TypeId ty) override
|
bool ignoreChildren(TypeId ty) override
|
||||||
{
|
{
|
||||||
if (get<ClassType>(ty))
|
if (get<ExternType>(ty))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
return ty->persistent;
|
return ty->persistent;
|
||||||
|
@ -397,12 +401,14 @@ TypePackId* SubtypingEnvironment::getMappedPackBounds(TypePackId tp)
|
||||||
Subtyping::Subtyping(
|
Subtyping::Subtyping(
|
||||||
NotNull<BuiltinTypes> builtinTypes,
|
NotNull<BuiltinTypes> builtinTypes,
|
||||||
NotNull<TypeArena> typeArena,
|
NotNull<TypeArena> typeArena,
|
||||||
|
NotNull<Simplifier> simplifier,
|
||||||
NotNull<Normalizer> normalizer,
|
NotNull<Normalizer> normalizer,
|
||||||
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
|
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
|
||||||
NotNull<InternalErrorReporter> iceReporter
|
NotNull<InternalErrorReporter> iceReporter
|
||||||
)
|
)
|
||||||
: builtinTypes(builtinTypes)
|
: builtinTypes(builtinTypes)
|
||||||
, arena(typeArena)
|
, arena(typeArena)
|
||||||
|
, simplifier(simplifier)
|
||||||
, normalizer(normalizer)
|
, normalizer(normalizer)
|
||||||
, typeFunctionRuntime(typeFunctionRuntime)
|
, typeFunctionRuntime(typeFunctionRuntime)
|
||||||
, iceReporter(iceReporter)
|
, iceReporter(iceReporter)
|
||||||
|
@ -415,6 +421,14 @@ SubtypingResult Subtyping::isSubtype(TypeId subTy, TypeId superTy, NotNull<Scope
|
||||||
|
|
||||||
SubtypingResult result = isCovariantWith(env, subTy, superTy, scope);
|
SubtypingResult result = isCovariantWith(env, subTy, superTy, scope);
|
||||||
|
|
||||||
|
if (result.normalizationTooComplex)
|
||||||
|
{
|
||||||
|
if (result.isCacheable)
|
||||||
|
resultCache[{subTy, superTy}] = result;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
for (const auto& [subTy, bounds] : env.mappedGenerics)
|
for (const auto& [subTy, bounds] : env.mappedGenerics)
|
||||||
{
|
{
|
||||||
const auto& lb = bounds.lowerBound;
|
const auto& lb = bounds.lowerBound;
|
||||||
|
@ -592,7 +606,12 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
|
||||||
if (!result.isSubtype && !result.normalizationTooComplex)
|
if (!result.isSubtype && !result.normalizationTooComplex)
|
||||||
{
|
{
|
||||||
SubtypingResult semantic = isCovariantWith(env, normalizer->normalize(subTy), normalizer->normalize(superTy), scope);
|
SubtypingResult semantic = isCovariantWith(env, normalizer->normalize(subTy), normalizer->normalize(superTy), scope);
|
||||||
if (semantic.isSubtype)
|
|
||||||
|
if (semantic.normalizationTooComplex)
|
||||||
|
{
|
||||||
|
result = semantic;
|
||||||
|
}
|
||||||
|
else if (semantic.isSubtype)
|
||||||
{
|
{
|
||||||
semantic.reasoning.clear();
|
semantic.reasoning.clear();
|
||||||
result = semantic;
|
result = semantic;
|
||||||
|
@ -607,7 +626,12 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
|
||||||
if (!result.isSubtype && !result.normalizationTooComplex)
|
if (!result.isSubtype && !result.normalizationTooComplex)
|
||||||
{
|
{
|
||||||
SubtypingResult semantic = isCovariantWith(env, normalizer->normalize(subTy), normalizer->normalize(superTy), scope);
|
SubtypingResult semantic = isCovariantWith(env, normalizer->normalize(subTy), normalizer->normalize(superTy), scope);
|
||||||
if (semantic.isSubtype)
|
|
||||||
|
if (semantic.normalizationTooComplex)
|
||||||
|
{
|
||||||
|
result = semantic;
|
||||||
|
}
|
||||||
|
else if (semantic.isSubtype)
|
||||||
{
|
{
|
||||||
// Clear the semantic reasoning, as any reasonings within
|
// Clear the semantic reasoning, as any reasonings within
|
||||||
// potentially contain invalid paths.
|
// potentially contain invalid paths.
|
||||||
|
@ -718,9 +742,9 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
|
||||||
result = isCovariantWith(env, p, scope);
|
result = isCovariantWith(env, p, scope);
|
||||||
else if (auto p = get2<MetatableType, TableType>(subTy, superTy))
|
else if (auto p = get2<MetatableType, TableType>(subTy, superTy))
|
||||||
result = isCovariantWith(env, p, scope);
|
result = isCovariantWith(env, p, scope);
|
||||||
else if (auto p = get2<ClassType, ClassType>(subTy, superTy))
|
else if (auto p = get2<ExternType, ExternType>(subTy, superTy))
|
||||||
result = isCovariantWith(env, p, scope);
|
result = isCovariantWith(env, p, scope);
|
||||||
else if (auto p = get2<ClassType, TableType>(subTy, superTy))
|
else if (auto p = get2<ExternType, TableType>(subTy, superTy))
|
||||||
result = isCovariantWith(env, subTy, p.first, superTy, p.second, scope);
|
result = isCovariantWith(env, subTy, p.first, superTy, p.second, scope);
|
||||||
else if (auto p = get2<TableType, PrimitiveType>(subTy, superTy))
|
else if (auto p = get2<TableType, PrimitiveType>(subTy, superTy))
|
||||||
result = isCovariantWith(env, p, scope);
|
result = isCovariantWith(env, p, scope);
|
||||||
|
@ -753,7 +777,8 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
|
||||||
// Match head types pairwise
|
// Match head types pairwise
|
||||||
|
|
||||||
for (size_t i = 0; i < headSize; ++i)
|
for (size_t i = 0; i < headSize; ++i)
|
||||||
results.push_back(isCovariantWith(env, subHead[i], superHead[i], scope).withBothComponent(TypePath::Index{i}));
|
results.push_back(isCovariantWith(env, subHead[i], superHead[i], scope).withBothComponent(TypePath::Index{i, TypePath::Index::Variant::Pack})
|
||||||
|
);
|
||||||
|
|
||||||
// Handle mismatched head sizes
|
// Handle mismatched head sizes
|
||||||
|
|
||||||
|
@ -766,7 +791,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
|
||||||
for (size_t i = headSize; i < superHead.size(); ++i)
|
for (size_t i = headSize; i < superHead.size(); ++i)
|
||||||
results.push_back(isCovariantWith(env, vt->ty, superHead[i], scope)
|
results.push_back(isCovariantWith(env, vt->ty, superHead[i], scope)
|
||||||
.withSubPath(TypePath::PathBuilder().tail().variadic().build())
|
.withSubPath(TypePath::PathBuilder().tail().variadic().build())
|
||||||
.withSuperComponent(TypePath::Index{i}));
|
.withSuperComponent(TypePath::Index{i, TypePath::Index::Variant::Pack}));
|
||||||
}
|
}
|
||||||
else if (auto gt = get<GenericTypePack>(*subTail))
|
else if (auto gt = get<GenericTypePack>(*subTail))
|
||||||
{
|
{
|
||||||
|
@ -820,7 +845,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
|
||||||
{
|
{
|
||||||
for (size_t i = headSize; i < subHead.size(); ++i)
|
for (size_t i = headSize; i < subHead.size(); ++i)
|
||||||
results.push_back(isCovariantWith(env, subHead[i], vt->ty, scope)
|
results.push_back(isCovariantWith(env, subHead[i], vt->ty, scope)
|
||||||
.withSubComponent(TypePath::Index{i})
|
.withSubComponent(TypePath::Index{i, TypePath::Index::Variant::Pack})
|
||||||
.withSuperPath(TypePath::PathBuilder().tail().variadic().build()));
|
.withSuperPath(TypePath::PathBuilder().tail().variadic().build()));
|
||||||
}
|
}
|
||||||
else if (auto gt = get<GenericTypePack>(*superTail))
|
else if (auto gt = get<GenericTypePack>(*superTail))
|
||||||
|
@ -858,7 +883,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
|
||||||
else
|
else
|
||||||
return SubtypingResult{false}
|
return SubtypingResult{false}
|
||||||
.withSuperComponent(TypePath::PackField::Tail)
|
.withSuperComponent(TypePath::PackField::Tail)
|
||||||
.withError({scope->location, UnexpectedTypePackInSubtyping{*subTail}});
|
.withError({scope->location, UnexpectedTypePackInSubtyping{*superTail}});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
return {false};
|
return {false};
|
||||||
|
@ -1081,6 +1106,10 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
|
||||||
for (TypeId ty : superUnion)
|
for (TypeId ty : superUnion)
|
||||||
{
|
{
|
||||||
SubtypingResult next = isCovariantWith(env, subTy, ty, scope);
|
SubtypingResult next = isCovariantWith(env, subTy, ty, scope);
|
||||||
|
|
||||||
|
if (next.normalizationTooComplex)
|
||||||
|
return SubtypingResult{false, /* normalizationTooComplex */ true};
|
||||||
|
|
||||||
if (next.isSubtype)
|
if (next.isSubtype)
|
||||||
return SubtypingResult{true};
|
return SubtypingResult{true};
|
||||||
}
|
}
|
||||||
|
@ -1099,7 +1128,13 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Unio
|
||||||
std::vector<SubtypingResult> subtypings;
|
std::vector<SubtypingResult> subtypings;
|
||||||
size_t i = 0;
|
size_t i = 0;
|
||||||
for (TypeId ty : subUnion)
|
for (TypeId ty : subUnion)
|
||||||
subtypings.push_back(isCovariantWith(env, ty, superTy, scope).withSubComponent(TypePath::Index{i++}));
|
{
|
||||||
|
subtypings.push_back(isCovariantWith(env, ty, superTy, scope).withSubComponent(TypePath::Index{i++, TypePath::Index::Variant::Union}));
|
||||||
|
|
||||||
|
if (subtypings.back().normalizationTooComplex)
|
||||||
|
return SubtypingResult{false, /* normalizationTooComplex */ true};
|
||||||
|
}
|
||||||
|
|
||||||
return SubtypingResult::all(subtypings);
|
return SubtypingResult::all(subtypings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1109,7 +1144,13 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
|
||||||
std::vector<SubtypingResult> subtypings;
|
std::vector<SubtypingResult> subtypings;
|
||||||
size_t i = 0;
|
size_t i = 0;
|
||||||
for (TypeId ty : superIntersection)
|
for (TypeId ty : superIntersection)
|
||||||
subtypings.push_back(isCovariantWith(env, subTy, ty, scope).withSuperComponent(TypePath::Index{i++}));
|
{
|
||||||
|
subtypings.push_back(isCovariantWith(env, subTy, ty, scope).withSuperComponent(TypePath::Index{i++, TypePath::Index::Variant::Intersection}));
|
||||||
|
|
||||||
|
if (subtypings.back().normalizationTooComplex)
|
||||||
|
return SubtypingResult{false, /* normalizationTooComplex */ true};
|
||||||
|
}
|
||||||
|
|
||||||
return SubtypingResult::all(subtypings);
|
return SubtypingResult::all(subtypings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1119,7 +1160,13 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Inte
|
||||||
std::vector<SubtypingResult> subtypings;
|
std::vector<SubtypingResult> subtypings;
|
||||||
size_t i = 0;
|
size_t i = 0;
|
||||||
for (TypeId ty : subIntersection)
|
for (TypeId ty : subIntersection)
|
||||||
subtypings.push_back(isCovariantWith(env, ty, superTy, scope).withSubComponent(TypePath::Index{i++}));
|
{
|
||||||
|
subtypings.push_back(isCovariantWith(env, ty, superTy, scope).withSubComponent(TypePath::Index{i++, TypePath::Index::Variant::Intersection}));
|
||||||
|
|
||||||
|
if (subtypings.back().normalizationTooComplex)
|
||||||
|
return SubtypingResult{false, /* normalizationTooComplex */ true};
|
||||||
|
}
|
||||||
|
|
||||||
return SubtypingResult::any(subtypings);
|
return SubtypingResult::any(subtypings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
// the top class type is not actually a primitive type, so the negation of
|
||||||
// any one of them includes the top class type.
|
// any one of them includes the top class type.
|
||||||
else if (auto p = get2<ClassType, PrimitiveType>(subTy, negatedTy))
|
else if (auto p = get2<ExternType, PrimitiveType>(subTy, negatedTy))
|
||||||
result = {true};
|
result = {true};
|
||||||
else if (auto p = get<PrimitiveType>(negatedTy); p && is<TableType, MetatableType>(subTy))
|
else if (auto p = get<PrimitiveType>(negatedTy); p && is<TableType, MetatableType>(subTy))
|
||||||
result = {p->type != PrimitiveType::Table};
|
result = {p->type != PrimitiveType::Table};
|
||||||
|
@ -1295,9 +1342,9 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Type
|
||||||
result = {p.second->type != PrimitiveType::Function};
|
result = {p.second->type != PrimitiveType::Function};
|
||||||
else if (auto p = get2<SingletonType, SingletonType>(subTy, negatedTy))
|
else if (auto p = get2<SingletonType, SingletonType>(subTy, negatedTy))
|
||||||
result = {*p.first != *p.second};
|
result = {*p.first != *p.second};
|
||||||
else if (auto p = get2<ClassType, ClassType>(subTy, negatedTy))
|
else if (auto p = get2<ExternType, ExternType>(subTy, negatedTy))
|
||||||
result = SubtypingResult::negate(isCovariantWith(env, p.first, p.second, scope));
|
result = SubtypingResult::negate(isCovariantWith(env, p.first, p.second, scope));
|
||||||
else if (get2<FunctionType, ClassType>(subTy, negatedTy))
|
else if (get2<FunctionType, ExternType>(subTy, negatedTy))
|
||||||
result = {true};
|
result = {true};
|
||||||
else if (is<ErrorType, FunctionType, TableType, MetatableType>(negatedTy))
|
else if (is<ErrorType, FunctionType, TableType, MetatableType>(negatedTy))
|
||||||
iceReporter->ice("attempting to negate a non-testable type");
|
iceReporter->ice("attempting to negate a non-testable type");
|
||||||
|
@ -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)
|
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)
|
||||||
return isCovariantWith(env, subMt->table, superMt->table, scope)
|
.andAlso(isCovariantWith(env, subMt->metatable, superMt->metatable, scope).withBothComponent(TypePath::TypeField::Metatable));
|
||||||
.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));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const MetatableType* subMt, const TableType* superTable, NotNull<Scope> scope)
|
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.
|
// of the supertype table.
|
||||||
//
|
//
|
||||||
// There's a flaw here in that if the __index metamethod contributes a new
|
// There's a flaw here in that if the __index metamethod contributes a new
|
||||||
// field that would satisfy the subtyping relationship, we'll erronously say
|
// field that would satisfy the subtyping relationship, we'll erroneously say
|
||||||
// that the metatable isn't a subtype of the table, even though they have
|
// that the metatable isn't a subtype of the table, even though they have
|
||||||
// compatible properties/shapes. We'll revisit this later when we have a
|
// compatible properties/shapes. We'll revisit this later when we have a
|
||||||
// better understanding of how important this is.
|
// better understanding of how important this is.
|
||||||
|
@ -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(
|
SubtypingResult Subtyping::isCovariantWith(
|
||||||
SubtypingEnvironment& env,
|
SubtypingEnvironment& env,
|
||||||
TypeId subTy,
|
TypeId subTy,
|
||||||
const ClassType* subClass,
|
const ExternType* subExternType,
|
||||||
TypeId superTy,
|
TypeId superTy,
|
||||||
const TableType* superTable,
|
const TableType* superTable,
|
||||||
NotNull<Scope> scope
|
NotNull<Scope> scope
|
||||||
|
@ -1450,7 +1489,7 @@ SubtypingResult Subtyping::isCovariantWith(
|
||||||
|
|
||||||
for (const auto& [name, prop] : superTable->props)
|
for (const auto& [name, prop] : superTable->props)
|
||||||
{
|
{
|
||||||
if (auto classProp = lookupClassProp(subClass, name))
|
if (auto classProp = lookupExternTypeProp(subExternType, name))
|
||||||
{
|
{
|
||||||
result.andAlso(isCovariantWith(env, *classProp, prop, name, scope));
|
result.andAlso(isCovariantWith(env, *classProp, prop, name, scope));
|
||||||
}
|
}
|
||||||
|
@ -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.
|
// 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.
|
// 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);
|
auto [arguments, tail] = flatten(superFunction->argTypes);
|
||||||
|
|
||||||
if (auto variadic = get<VariadicTypePack>(tail); variadic && variadic->hidden)
|
if (auto variadic = get<VariadicTypePack>(tail); variadic && variadic->hidden)
|
||||||
{
|
{
|
||||||
result.orElse(
|
result.orElse(isContravariantWith(env, subFunction->argTypes, arena->addTypePack(TypePack{arguments}), scope)
|
||||||
isContravariantWith(env, subFunction->argTypes, arena->addTypePack(TypePack{arguments}), scope).withBothComponent(TypePath::PackField::Arguments)
|
.withBothComponent(TypePath::PackField::Arguments));
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1621,7 +1659,7 @@ SubtypingResult Subtyping::isCovariantWith(
|
||||||
SubtypingResult result = isCovariantWith(env, subNorm->tops, superNorm->tops, scope);
|
SubtypingResult result = isCovariantWith(env, subNorm->tops, superNorm->tops, scope);
|
||||||
result.andAlso(isCovariantWith(env, subNorm->booleans, superNorm->booleans, scope));
|
result.andAlso(isCovariantWith(env, subNorm->booleans, superNorm->booleans, scope));
|
||||||
result.andAlso(
|
result.andAlso(
|
||||||
isCovariantWith(env, subNorm->classes, superNorm->classes, scope).orElse(isCovariantWith(env, subNorm->classes, superNorm->tables, scope))
|
isCovariantWith(env, subNorm->externTypes, superNorm->externTypes, scope).orElse(isCovariantWith(env, subNorm->externTypes, superNorm->tables, scope))
|
||||||
);
|
);
|
||||||
result.andAlso(isCovariantWith(env, subNorm->errors, superNorm->errors, scope));
|
result.andAlso(isCovariantWith(env, subNorm->errors, superNorm->errors, scope));
|
||||||
result.andAlso(isCovariantWith(env, subNorm->nils, superNorm->nils, scope));
|
result.andAlso(isCovariantWith(env, subNorm->nils, superNorm->nils, scope));
|
||||||
|
@ -1638,24 +1676,24 @@ SubtypingResult Subtyping::isCovariantWith(
|
||||||
|
|
||||||
SubtypingResult Subtyping::isCovariantWith(
|
SubtypingResult Subtyping::isCovariantWith(
|
||||||
SubtypingEnvironment& env,
|
SubtypingEnvironment& env,
|
||||||
const NormalizedClassType& subClass,
|
const NormalizedExternType& subExternType,
|
||||||
const NormalizedClassType& superClass,
|
const NormalizedExternType& superExternType,
|
||||||
NotNull<Scope> scope
|
NotNull<Scope> scope
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
for (const auto& [subClassTy, _] : subClass.classes)
|
for (const auto& [subExternTypeTy, _] : subExternType.externTypes)
|
||||||
{
|
{
|
||||||
SubtypingResult result;
|
SubtypingResult result;
|
||||||
|
|
||||||
for (const auto& [superClassTy, superNegations] : superClass.classes)
|
for (const auto& [superExternTypeTy, superNegations] : superExternType.externTypes)
|
||||||
{
|
{
|
||||||
result.orElse(isCovariantWith(env, subClassTy, superClassTy, scope));
|
result.orElse(isCovariantWith(env, subExternTypeTy, superExternTypeTy, scope));
|
||||||
if (!result.isSubtype)
|
if (!result.isSubtype)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
for (TypeId negation : superNegations)
|
for (TypeId negation : superNegations)
|
||||||
{
|
{
|
||||||
result.andAlso(SubtypingResult::negate(isCovariantWith(env, subClassTy, negation, scope)));
|
result.andAlso(SubtypingResult::negate(isCovariantWith(env, subExternTypeTy, negation, scope)));
|
||||||
if (result.isSubtype)
|
if (result.isSubtype)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -1670,17 +1708,17 @@ SubtypingResult Subtyping::isCovariantWith(
|
||||||
|
|
||||||
SubtypingResult Subtyping::isCovariantWith(
|
SubtypingResult Subtyping::isCovariantWith(
|
||||||
SubtypingEnvironment& env,
|
SubtypingEnvironment& env,
|
||||||
const NormalizedClassType& subClass,
|
const NormalizedExternType& subExternType,
|
||||||
const TypeIds& superTables,
|
const TypeIds& superTables,
|
||||||
NotNull<Scope> scope
|
NotNull<Scope> scope
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
for (const auto& [subClassTy, _] : subClass.classes)
|
for (const auto& [subExternTypeTy, _] : subExternType.externTypes)
|
||||||
{
|
{
|
||||||
SubtypingResult result;
|
SubtypingResult result;
|
||||||
|
|
||||||
for (TypeId superTableTy : superTables)
|
for (TypeId superTableTy : superTables)
|
||||||
result.orElse(isCovariantWith(env, subClassTy, superTableTy, scope));
|
result.orElse(isCovariantWith(env, subExternTypeTy, superTableTy, scope));
|
||||||
|
|
||||||
if (!result.isSubtype)
|
if (!result.isSubtype)
|
||||||
return result;
|
return result;
|
||||||
|
@ -1768,7 +1806,12 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Type
|
||||||
{
|
{
|
||||||
results.emplace_back();
|
results.emplace_back();
|
||||||
for (TypeId superTy : superTypes)
|
for (TypeId superTy : superTypes)
|
||||||
|
{
|
||||||
results.back().orElse(isCovariantWith(env, subTy, superTy, scope));
|
results.back().orElse(isCovariantWith(env, subTy, superTy, scope));
|
||||||
|
|
||||||
|
if (results.back().normalizationTooComplex)
|
||||||
|
return SubtypingResult{false, /* normalizationTooComplex */ true};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return SubtypingResult::all(results);
|
return SubtypingResult::all(results);
|
||||||
|
@ -1870,7 +1913,7 @@ TypeId Subtyping::makeAggregateType(const Container& container, TypeId orElse)
|
||||||
|
|
||||||
std::pair<TypeId, ErrorVec> Subtyping::handleTypeFunctionReductionResult(const TypeFunctionInstanceType* functionInstance, NotNull<Scope> scope)
|
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);
|
TypeId function = arena->addType(*functionInstance);
|
||||||
FunctionGraphReductionResult result = reduceTypeFunctions(function, {}, context, true);
|
FunctionGraphReductionResult result = reduceTypeFunctions(function, {}, context, true);
|
||||||
ErrorVec errors;
|
ErrorVec errors;
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
#include "Luau/Common.h"
|
#include "Luau/Common.h"
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauSolverV2)
|
LUAU_FASTFLAG(LuauSolverV2)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauSymbolEquality)
|
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
@ -15,10 +14,8 @@ bool Symbol::operator==(const Symbol& rhs) const
|
||||||
return local == rhs.local;
|
return local == rhs.local;
|
||||||
else if (global.value)
|
else if (global.value)
|
||||||
return rhs.global.value && global == rhs.global.value; // Subtlety: AstName::operator==(const char*) uses strcmp, not pointer identity.
|
return rhs.global.value && global == rhs.global.value; // Subtlety: AstName::operator==(const char*) uses strcmp, not pointer identity.
|
||||||
else if (FFlag::LuauSolverV2 || FFlag::LuauSymbolEquality)
|
|
||||||
return !rhs.local && !rhs.global.value; // Reflexivity: we already know `this` Symbol is empty, so check that rhs is.
|
|
||||||
else
|
else
|
||||||
return false;
|
return !rhs.local && !rhs.global.value; // Reflexivity: we already know `this` Symbol is empty, so check that rhs is.
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string toString(const Symbol& name)
|
std::string toString(const Symbol& name)
|
||||||
|
|
|
@ -1,15 +1,21 @@
|
||||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
|
||||||
|
#include "Luau/TableLiteralInference.h"
|
||||||
|
|
||||||
#include "Luau/Ast.h"
|
#include "Luau/Ast.h"
|
||||||
|
#include "Luau/Common.h"
|
||||||
#include "Luau/Normalize.h"
|
#include "Luau/Normalize.h"
|
||||||
#include "Luau/Simplify.h"
|
#include "Luau/Simplify.h"
|
||||||
|
#include "Luau/Subtyping.h"
|
||||||
#include "Luau/Type.h"
|
#include "Luau/Type.h"
|
||||||
#include "Luau/ToString.h"
|
#include "Luau/ToString.h"
|
||||||
#include "Luau/TypeArena.h"
|
#include "Luau/TypeArena.h"
|
||||||
#include "Luau/TypeUtils.h"
|
#include "Luau/TypeUtils.h"
|
||||||
#include "Luau/Unifier2.h"
|
#include "Luau/Unifier2.h"
|
||||||
|
|
||||||
LUAU_DYNAMIC_FASTINT(LuauTypeSolverRelease)
|
LUAU_FASTFLAGVARIABLE(LuauBidirectionalInferenceCollectIndexerTypes)
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauBidirectionalFailsafe)
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauBidirectionalInferenceElideAssert)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
@ -111,6 +117,7 @@ TypeId matchLiteralType(
|
||||||
NotNull<BuiltinTypes> builtinTypes,
|
NotNull<BuiltinTypes> builtinTypes,
|
||||||
NotNull<TypeArena> arena,
|
NotNull<TypeArena> arena,
|
||||||
NotNull<Unifier2> unifier,
|
NotNull<Unifier2> unifier,
|
||||||
|
NotNull<Subtyping> subtyping,
|
||||||
TypeId expectedType,
|
TypeId expectedType,
|
||||||
TypeId exprType,
|
TypeId exprType,
|
||||||
const AstExpr* expr,
|
const AstExpr* expr,
|
||||||
|
@ -131,17 +138,33 @@ TypeId matchLiteralType(
|
||||||
* things like replace explicit named properties with indexers as required
|
* things like replace explicit named properties with indexers as required
|
||||||
* by the expected type.
|
* by the expected type.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if (!isLiteral(expr))
|
if (!isLiteral(expr))
|
||||||
return exprType;
|
{
|
||||||
|
auto result = subtyping->isSubtype(/*subTy=*/exprType, /*superTy=*/expectedType, unifier->scope);
|
||||||
|
return result.isSubtype ? expectedType : exprType;
|
||||||
|
}
|
||||||
|
|
||||||
expectedType = follow(expectedType);
|
expectedType = follow(expectedType);
|
||||||
exprType = follow(exprType);
|
exprType = follow(exprType);
|
||||||
|
|
||||||
if (get<AnyType>(expectedType) || get<UnknownType>(expectedType))
|
if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes)
|
||||||
{
|
{
|
||||||
// "Narrowing" to unknown or any is not going to do anything useful.
|
// The intent of `matchLiteralType` is to upcast values when it's safe
|
||||||
return exprType;
|
// to do so. it's always safe to upcast to `any` or `unknown`, so we
|
||||||
|
// can unconditionally do so here.
|
||||||
|
if (is<AnyType, UnknownType>(expectedType))
|
||||||
|
return expectedType;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (get<AnyType>(expectedType) || get<UnknownType>(expectedType))
|
||||||
|
{
|
||||||
|
// "Narrowing" to unknown or any is not going to do anything useful.
|
||||||
|
return exprType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if (expr->is<AstExprConstantString>())
|
if (expr->is<AstExprConstantString>())
|
||||||
{
|
{
|
||||||
|
@ -209,11 +232,27 @@ TypeId matchLiteralType(
|
||||||
return exprType;
|
return exprType;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: lambdas
|
|
||||||
|
if (expr->is<AstExprFunction>())
|
||||||
|
{
|
||||||
|
// TODO: Push argument / return types into the lambda. For now, just do
|
||||||
|
// the non-literal thing: check for a subtype and upcast if valid.
|
||||||
|
auto result = subtyping->isSubtype(/*subTy=*/exprType, /*superTy=*/expectedType, unifier->scope);
|
||||||
|
return result.isSubtype ? expectedType : exprType;
|
||||||
|
}
|
||||||
|
|
||||||
if (auto exprTable = expr->as<AstExprTable>())
|
if (auto exprTable = expr->as<AstExprTable>())
|
||||||
{
|
{
|
||||||
TableType* const tableTy = getMutable<TableType>(exprType);
|
TableType* const tableTy = getMutable<TableType>(exprType);
|
||||||
|
|
||||||
|
// This can occur if we have an expression like:
|
||||||
|
//
|
||||||
|
// { x = {}, x = 42 }
|
||||||
|
//
|
||||||
|
// The type of this will be `{ x: number }`
|
||||||
|
if (FFlag::LuauBidirectionalFailsafe && !tableTy)
|
||||||
|
return exprType;
|
||||||
|
|
||||||
LUAU_ASSERT(tableTy);
|
LUAU_ASSERT(tableTy);
|
||||||
|
|
||||||
const TableType* expectedTableTy = get<TableType>(expectedType);
|
const TableType* expectedTableTy = get<TableType>(expectedType);
|
||||||
|
@ -228,7 +267,7 @@ TypeId matchLiteralType(
|
||||||
|
|
||||||
if (tt)
|
if (tt)
|
||||||
{
|
{
|
||||||
TypeId res = matchLiteralType(astTypes, astExpectedTypes, builtinTypes, arena, unifier, *tt, exprType, expr, toBlock);
|
TypeId res = matchLiteralType(astTypes, astExpectedTypes, builtinTypes, arena, unifier, subtyping, *tt, exprType, expr, toBlock);
|
||||||
|
|
||||||
parts.push_back(res);
|
parts.push_back(res);
|
||||||
return arena->addType(UnionType{std::move(parts)});
|
return arena->addType(UnionType{std::move(parts)});
|
||||||
|
@ -238,6 +277,11 @@ TypeId matchLiteralType(
|
||||||
return exprType;
|
return exprType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DenseHashSet<AstExprConstantString*> keysToDelete{nullptr};
|
||||||
|
|
||||||
|
DenseHashSet<TypeId> indexerKeyTypes{nullptr};
|
||||||
|
DenseHashSet<TypeId> indexerValueTypes{nullptr};
|
||||||
|
|
||||||
for (const AstExprTable::Item& item : exprTable->items)
|
for (const AstExprTable::Item& item : exprTable->items)
|
||||||
{
|
{
|
||||||
if (isRecord(item))
|
if (isRecord(item))
|
||||||
|
@ -245,12 +289,20 @@ TypeId matchLiteralType(
|
||||||
const AstArray<char>& s = item.key->as<AstExprConstantString>()->value;
|
const AstArray<char>& s = item.key->as<AstExprConstantString>()->value;
|
||||||
std::string keyStr{s.data, s.data + s.size};
|
std::string keyStr{s.data, s.data + s.size};
|
||||||
auto it = tableTy->props.find(keyStr);
|
auto it = tableTy->props.find(keyStr);
|
||||||
|
|
||||||
|
// This can occur, potentially, if we are re-entrant.
|
||||||
|
if (FFlag::LuauBidirectionalFailsafe && it == tableTy->props.end())
|
||||||
|
continue;
|
||||||
|
|
||||||
LUAU_ASSERT(it != tableTy->props.end());
|
LUAU_ASSERT(it != tableTy->props.end());
|
||||||
|
|
||||||
Property& prop = it->second;
|
Property& prop = it->second;
|
||||||
|
|
||||||
// Table literals always initially result in shared read-write types
|
// If we encounter a duplcate property, we may have already
|
||||||
LUAU_ASSERT(prop.isShared());
|
// set it to be read-only. If that's the case, the only thing
|
||||||
|
// that will definitely crash is trying to access a write
|
||||||
|
// only property.
|
||||||
|
LUAU_ASSERT(!prop.isWriteOnly());
|
||||||
TypeId propTy = *prop.readTy;
|
TypeId propTy = *prop.readTy;
|
||||||
|
|
||||||
auto it2 = expectedTableTy->props.find(keyStr);
|
auto it2 = expectedTableTy->props.find(keyStr);
|
||||||
|
@ -271,18 +323,27 @@ TypeId matchLiteralType(
|
||||||
builtinTypes,
|
builtinTypes,
|
||||||
arena,
|
arena,
|
||||||
unifier,
|
unifier,
|
||||||
|
subtyping,
|
||||||
expectedTableTy->indexer->indexResultType,
|
expectedTableTy->indexer->indexResultType,
|
||||||
propTy,
|
propTy,
|
||||||
item.value,
|
item.value,
|
||||||
toBlock
|
toBlock
|
||||||
);
|
);
|
||||||
|
|
||||||
if (tableTy->indexer)
|
if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes)
|
||||||
unifier->unify(matchedType, tableTy->indexer->indexResultType);
|
{
|
||||||
|
indexerKeyTypes.insert(arena->addType(SingletonType{StringSingleton{keyStr}}));
|
||||||
|
indexerValueTypes.insert(matchedType);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
tableTy->indexer = TableIndexer{expectedTableTy->indexer->indexType, matchedType};
|
{
|
||||||
|
if (tableTy->indexer)
|
||||||
|
unifier->unify(matchedType, tableTy->indexer->indexResultType);
|
||||||
|
else
|
||||||
|
tableTy->indexer = TableIndexer{expectedTableTy->indexer->indexType, matchedType};
|
||||||
|
}
|
||||||
|
|
||||||
tableTy->props.erase(keyStr);
|
keysToDelete.insert(item.key->as<AstExprConstantString>());
|
||||||
}
|
}
|
||||||
|
|
||||||
// If it's just an extra property and the expected type
|
// If it's just an extra property and the expected type
|
||||||
|
@ -305,22 +366,25 @@ TypeId matchLiteralType(
|
||||||
// quadratic in a hurry.
|
// quadratic in a hurry.
|
||||||
if (expectedProp.isShared())
|
if (expectedProp.isShared())
|
||||||
{
|
{
|
||||||
matchedType =
|
matchedType = matchLiteralType(
|
||||||
matchLiteralType(astTypes, astExpectedTypes, builtinTypes, arena, unifier, *expectedReadTy, propTy, item.value, toBlock);
|
astTypes, astExpectedTypes, builtinTypes, arena, unifier, subtyping, *expectedReadTy, propTy, item.value, toBlock
|
||||||
|
);
|
||||||
prop.readTy = matchedType;
|
prop.readTy = matchedType;
|
||||||
prop.writeTy = matchedType;
|
prop.writeTy = matchedType;
|
||||||
}
|
}
|
||||||
else if (expectedReadTy)
|
else if (expectedReadTy)
|
||||||
{
|
{
|
||||||
matchedType =
|
matchedType = matchLiteralType(
|
||||||
matchLiteralType(astTypes, astExpectedTypes, builtinTypes, arena, unifier, *expectedReadTy, propTy, item.value, toBlock);
|
astTypes, astExpectedTypes, builtinTypes, arena, unifier, subtyping, *expectedReadTy, propTy, item.value, toBlock
|
||||||
|
);
|
||||||
prop.readTy = matchedType;
|
prop.readTy = matchedType;
|
||||||
prop.writeTy.reset();
|
prop.writeTy.reset();
|
||||||
}
|
}
|
||||||
else if (expectedWriteTy)
|
else if (expectedWriteTy)
|
||||||
{
|
{
|
||||||
matchedType =
|
matchedType = matchLiteralType(
|
||||||
matchLiteralType(astTypes, astExpectedTypes, builtinTypes, arena, unifier, *expectedWriteTy, propTy, item.value, toBlock);
|
astTypes, astExpectedTypes, builtinTypes, arena, unifier, subtyping, *expectedWriteTy, propTy, item.value, toBlock
|
||||||
|
);
|
||||||
prop.readTy.reset();
|
prop.readTy.reset();
|
||||||
prop.writeTy = matchedType;
|
prop.writeTy = matchedType;
|
||||||
}
|
}
|
||||||
|
@ -337,10 +401,16 @@ TypeId matchLiteralType(
|
||||||
LUAU_ASSERT(matchedType);
|
LUAU_ASSERT(matchedType);
|
||||||
|
|
||||||
(*astExpectedTypes)[item.value] = matchedType;
|
(*astExpectedTypes)[item.value] = matchedType;
|
||||||
|
// NOTE: We do *not* add to the potential indexer types here.
|
||||||
|
// I think this is correct to support something like:
|
||||||
|
//
|
||||||
|
// { [string]: number, foo: boolean }
|
||||||
|
//
|
||||||
}
|
}
|
||||||
else if (item.kind == AstExprTable::Item::List)
|
else if (item.kind == AstExprTable::Item::List)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(tableTy->indexer);
|
if (!FFlag::LuauBidirectionalInferenceCollectIndexerTypes || !FFlag::LuauBidirectionalInferenceElideAssert)
|
||||||
|
LUAU_ASSERT(tableTy->indexer);
|
||||||
|
|
||||||
if (expectedTableTy->indexer)
|
if (expectedTableTy->indexer)
|
||||||
{
|
{
|
||||||
|
@ -354,15 +424,24 @@ TypeId matchLiteralType(
|
||||||
builtinTypes,
|
builtinTypes,
|
||||||
arena,
|
arena,
|
||||||
unifier,
|
unifier,
|
||||||
|
subtyping,
|
||||||
expectedTableTy->indexer->indexResultType,
|
expectedTableTy->indexer->indexResultType,
|
||||||
*propTy,
|
*propTy,
|
||||||
item.value,
|
item.value,
|
||||||
toBlock
|
toBlock
|
||||||
);
|
);
|
||||||
|
|
||||||
// if the index result type is the prop type, we can replace it with the matched type here.
|
if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes)
|
||||||
if (tableTy->indexer->indexResultType == *propTy)
|
{
|
||||||
tableTy->indexer->indexResultType = matchedType;
|
indexerKeyTypes.insert(builtinTypes->numberType);
|
||||||
|
indexerValueTypes.insert(matchedType);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// if the index result type is the prop type, we can replace it with the matched type here.
|
||||||
|
if (tableTy->indexer->indexResultType == *propTy)
|
||||||
|
tableTy->indexer->indexResultType = matchedType;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (item.kind == AstExprTable::Item::General)
|
else if (item.kind == AstExprTable::Item::General)
|
||||||
|
@ -376,29 +455,32 @@ TypeId matchLiteralType(
|
||||||
const TypeId* keyTy = astTypes->find(item.key);
|
const TypeId* keyTy = astTypes->find(item.key);
|
||||||
LUAU_ASSERT(keyTy);
|
LUAU_ASSERT(keyTy);
|
||||||
TypeId tKey = follow(*keyTy);
|
TypeId tKey = follow(*keyTy);
|
||||||
if (DFInt::LuauTypeSolverRelease >= 648)
|
LUAU_ASSERT(!is<BlockedType>(tKey));
|
||||||
{
|
|
||||||
LUAU_ASSERT(!is<BlockedType>(tKey));
|
|
||||||
}
|
|
||||||
else if (get<BlockedType>(tKey))
|
|
||||||
toBlock.push_back(tKey);
|
|
||||||
const TypeId* propTy = astTypes->find(item.value);
|
const TypeId* propTy = astTypes->find(item.value);
|
||||||
LUAU_ASSERT(propTy);
|
LUAU_ASSERT(propTy);
|
||||||
TypeId tProp = follow(*propTy);
|
TypeId tProp = follow(*propTy);
|
||||||
if (DFInt::LuauTypeSolverRelease >= 648)
|
LUAU_ASSERT(!is<BlockedType>(tProp));
|
||||||
{
|
|
||||||
LUAU_ASSERT(!is<BlockedType>(tKey));
|
|
||||||
}
|
|
||||||
else if (get<BlockedType>(tProp))
|
|
||||||
toBlock.push_back(tProp);
|
|
||||||
// Populate expected types for non-string keys declared with [] (the code below will handle the case where they are strings)
|
// Populate expected types for non-string keys declared with [] (the code below will handle the case where they are strings)
|
||||||
if (!item.key->as<AstExprConstantString>() && expectedTableTy->indexer)
|
if (!item.key->as<AstExprConstantString>() && expectedTableTy->indexer)
|
||||||
(*astExpectedTypes)[item.key] = expectedTableTy->indexer->indexType;
|
(*astExpectedTypes)[item.key] = expectedTableTy->indexer->indexType;
|
||||||
|
|
||||||
|
if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes)
|
||||||
|
{
|
||||||
|
indexerKeyTypes.insert(tKey);
|
||||||
|
indexerValueTypes.insert(tProp);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
LUAU_ASSERT(!"Unexpected");
|
LUAU_ASSERT(!"Unexpected");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const auto& key : keysToDelete)
|
||||||
|
{
|
||||||
|
const AstArray<char>& s = key->value;
|
||||||
|
std::string keyStr{s.data, s.data + s.size};
|
||||||
|
tableTy->props.erase(keyStr);
|
||||||
|
}
|
||||||
|
|
||||||
// Keys that the expectedType says we should have, but that aren't
|
// Keys that the expectedType says we should have, but that aren't
|
||||||
// specified by the AST fragment.
|
// specified by the AST fragment.
|
||||||
//
|
//
|
||||||
|
@ -448,9 +530,39 @@ TypeId matchLiteralType(
|
||||||
// have one too.
|
// have one too.
|
||||||
// TODO: If the expected table also has an indexer, we might want to
|
// TODO: If the expected table also has an indexer, we might want to
|
||||||
// push the expected indexer's types into it.
|
// push the expected indexer's types into it.
|
||||||
if (expectedTableTy->indexer && !tableTy->indexer)
|
if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes && expectedTableTy->indexer)
|
||||||
{
|
{
|
||||||
tableTy->indexer = expectedTableTy->indexer;
|
if (indexerValueTypes.size() > 0 && indexerKeyTypes.size() > 0)
|
||||||
|
{
|
||||||
|
TypeId inferredKeyType = builtinTypes->neverType;
|
||||||
|
TypeId inferredValueType = builtinTypes->neverType;
|
||||||
|
for (auto kt : indexerKeyTypes)
|
||||||
|
{
|
||||||
|
auto simplified = simplifyUnion(builtinTypes, arena, inferredKeyType, kt);
|
||||||
|
inferredKeyType = simplified.result;
|
||||||
|
}
|
||||||
|
for (auto vt : indexerValueTypes)
|
||||||
|
{
|
||||||
|
auto simplified = simplifyUnion(builtinTypes, arena, inferredValueType, vt);
|
||||||
|
inferredValueType = simplified.result;
|
||||||
|
}
|
||||||
|
tableTy->indexer = TableIndexer{inferredKeyType, inferredValueType};
|
||||||
|
auto keyCheck = subtyping->isSubtype(inferredKeyType, expectedTableTy->indexer->indexType, unifier->scope);
|
||||||
|
if (keyCheck.isSubtype)
|
||||||
|
tableTy->indexer->indexType = expectedTableTy->indexer->indexType;
|
||||||
|
auto valueCheck = subtyping->isSubtype(inferredValueType, expectedTableTy->indexer->indexResultType, unifier->scope);
|
||||||
|
if (valueCheck.isSubtype)
|
||||||
|
tableTy->indexer->indexResultType = expectedTableTy->indexer->indexResultType;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LUAU_ASSERT(indexerKeyTypes.empty() && indexerValueTypes.empty());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (expectedTableTy->indexer && !tableTy->indexer)
|
||||||
|
{
|
||||||
|
tableTy->indexer = expectedTableTy->indexer;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -299,9 +299,9 @@ void StateDot::visitChildren(TypeId ty, int index)
|
||||||
finishNodeLabel(ty);
|
finishNodeLabel(ty);
|
||||||
finishNode();
|
finishNode();
|
||||||
}
|
}
|
||||||
else if constexpr (std::is_same_v<T, ClassType>)
|
else if constexpr (std::is_same_v<T, ExternType>)
|
||||||
{
|
{
|
||||||
formatAppend(result, "ClassType %s", t.name.c_str());
|
formatAppend(result, "ExternType %s", t.name.c_str());
|
||||||
finishNodeLabel(ty);
|
finishNodeLabel(ty);
|
||||||
finishNode();
|
finishNode();
|
||||||
|
|
||||||
|
@ -420,7 +420,7 @@ void StateDot::visitChildren(TypePackId tp, int index)
|
||||||
finishNodeLabel(tp);
|
finishNodeLabel(tp);
|
||||||
finishNode();
|
finishNode();
|
||||||
}
|
}
|
||||||
else if (get<Unifiable::Error>(tp))
|
else if (get<ErrorTypePack>(tp))
|
||||||
{
|
{
|
||||||
formatAppend(result, "ErrorTypePack %d", index);
|
formatAppend(result, "ErrorTypePack %d", index);
|
||||||
finishNodeLabel(tp);
|
finishNodeLabel(tp);
|
||||||
|
|
|
@ -19,7 +19,11 @@
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauEnableDenseTableAlias)
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauSolverV2)
|
LUAU_FASTFLAG(LuauSolverV2)
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauSyntheticErrors)
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauStringPartLengthLimit)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Enables increasing levels of verbosity for Luau type names when stringifying.
|
* Enables increasing levels of verbosity for Luau type names when stringifying.
|
||||||
|
@ -119,7 +123,7 @@ struct FindCyclicTypes final : TypeVisitor
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool visit(TypeId ty, const ClassType&) override
|
bool visit(TypeId ty, const ExternType&) override
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -300,6 +304,28 @@ struct StringifierState
|
||||||
emit(std::to_string(i).c_str());
|
emit(std::to_string(i).c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void emit(Polarity p)
|
||||||
|
{
|
||||||
|
switch (p)
|
||||||
|
{
|
||||||
|
case Polarity::None:
|
||||||
|
emit(" ");
|
||||||
|
break;
|
||||||
|
case Polarity::Negative:
|
||||||
|
emit(" -");
|
||||||
|
break;
|
||||||
|
case Polarity::Positive:
|
||||||
|
emit("+ ");
|
||||||
|
break;
|
||||||
|
case Polarity::Mixed:
|
||||||
|
emit("+-");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
emit("!!");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void indent()
|
void indent()
|
||||||
{
|
{
|
||||||
indentation += 4;
|
indentation += 4;
|
||||||
|
@ -481,6 +507,8 @@ struct TypeStringifier
|
||||||
{
|
{
|
||||||
state.emit("'");
|
state.emit("'");
|
||||||
state.emit(state.getName(ty));
|
state.emit(state.getName(ty));
|
||||||
|
if (FInt::DebugLuauVerboseTypeNames >= 1)
|
||||||
|
state.emit(ftv.polarity);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -493,6 +521,9 @@ struct TypeStringifier
|
||||||
state.emit("'");
|
state.emit("'");
|
||||||
state.emit(state.getName(ty));
|
state.emit(state.getName(ty));
|
||||||
|
|
||||||
|
if (FInt::DebugLuauVerboseTypeNames >= 1)
|
||||||
|
state.emit(ftv.polarity);
|
||||||
|
|
||||||
if (!get<UnknownType>(upperBound))
|
if (!get<UnknownType>(upperBound))
|
||||||
{
|
{
|
||||||
state.emit(" <: ");
|
state.emit(" <: ");
|
||||||
|
@ -508,6 +539,9 @@ struct TypeStringifier
|
||||||
|
|
||||||
state.emit(state.getName(ty));
|
state.emit(state.getName(ty));
|
||||||
|
|
||||||
|
if (FFlag::LuauSolverV2 && FInt::DebugLuauVerboseTypeNames >= 1)
|
||||||
|
state.emit(ftv.polarity);
|
||||||
|
|
||||||
if (FInt::DebugLuauVerboseTypeNames >= 2)
|
if (FInt::DebugLuauVerboseTypeNames >= 2)
|
||||||
{
|
{
|
||||||
state.emit("-");
|
state.emit("-");
|
||||||
|
@ -537,6 +571,9 @@ struct TypeStringifier
|
||||||
else
|
else
|
||||||
state.emit(state.getName(ty));
|
state.emit(state.getName(ty));
|
||||||
|
|
||||||
|
if (FInt::DebugLuauVerboseTypeNames >= 1)
|
||||||
|
state.emit(gtv.polarity);
|
||||||
|
|
||||||
if (FInt::DebugLuauVerboseTypeNames >= 2)
|
if (FInt::DebugLuauVerboseTypeNames >= 2)
|
||||||
{
|
{
|
||||||
state.emit("-");
|
state.emit("-");
|
||||||
|
@ -685,7 +722,13 @@ struct TypeStringifier
|
||||||
if (ttv.boundTo)
|
if (ttv.boundTo)
|
||||||
return stringify(*ttv.boundTo);
|
return stringify(*ttv.boundTo);
|
||||||
|
|
||||||
if (!state.exhaustive)
|
bool showName = !state.exhaustive;
|
||||||
|
if (FFlag::LuauEnableDenseTableAlias)
|
||||||
|
{
|
||||||
|
// if hide table alias expansions are enabled and there is a name found for the table, use it
|
||||||
|
showName = !state.exhaustive || state.opts.hideTableAliasExpansions;
|
||||||
|
}
|
||||||
|
if (showName)
|
||||||
{
|
{
|
||||||
if (ttv.name)
|
if (ttv.name)
|
||||||
{
|
{
|
||||||
|
@ -708,6 +751,10 @@ struct TypeStringifier
|
||||||
stringify(ttv.instantiatedTypeParams, ttv.instantiatedTypePackParams);
|
stringify(ttv.instantiatedTypeParams, ttv.instantiatedTypePackParams);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!state.exhaustive)
|
||||||
|
{
|
||||||
if (ttv.syntheticName)
|
if (ttv.syntheticName)
|
||||||
{
|
{
|
||||||
state.result.invalid = true;
|
state.result.invalid = true;
|
||||||
|
@ -846,9 +893,9 @@ struct TypeStringifier
|
||||||
state.emit(" }");
|
state.emit(" }");
|
||||||
}
|
}
|
||||||
|
|
||||||
void operator()(TypeId, const ClassType& ctv)
|
void operator()(TypeId, const ExternType& etv)
|
||||||
{
|
{
|
||||||
state.emit(ctv.name);
|
state.emit(etv.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
void operator()(TypeId, const AnyType&)
|
void operator()(TypeId, const AnyType&)
|
||||||
|
@ -876,6 +923,9 @@ struct TypeStringifier
|
||||||
bool hasNonNilDisjunct = false;
|
bool hasNonNilDisjunct = false;
|
||||||
|
|
||||||
std::vector<std::string> results = {};
|
std::vector<std::string> results = {};
|
||||||
|
size_t resultsLength = 0;
|
||||||
|
bool lengthLimitHit = false;
|
||||||
|
|
||||||
for (auto el : &uv)
|
for (auto el : &uv)
|
||||||
{
|
{
|
||||||
el = follow(el);
|
el = follow(el);
|
||||||
|
@ -902,14 +952,34 @@ struct TypeStringifier
|
||||||
if (needParens)
|
if (needParens)
|
||||||
state.emit(")");
|
state.emit(")");
|
||||||
|
|
||||||
|
if (FFlag::LuauStringPartLengthLimit)
|
||||||
|
resultsLength += state.result.name.length();
|
||||||
|
|
||||||
results.push_back(std::move(state.result.name));
|
results.push_back(std::move(state.result.name));
|
||||||
|
|
||||||
state.result.name = std::move(saved);
|
state.result.name = std::move(saved);
|
||||||
|
|
||||||
|
if (FFlag::LuauStringPartLengthLimit)
|
||||||
|
{
|
||||||
|
lengthLimitHit = state.opts.maxTypeLength > 0 && resultsLength > state.opts.maxTypeLength;
|
||||||
|
|
||||||
|
if (lengthLimitHit)
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
state.unsee(&uv);
|
state.unsee(&uv);
|
||||||
|
|
||||||
if (!FFlag::DebugLuauToStringNoLexicalSort)
|
if (FFlag::LuauStringPartLengthLimit)
|
||||||
std::sort(results.begin(), results.end());
|
{
|
||||||
|
if (!lengthLimitHit && !FFlag::DebugLuauToStringNoLexicalSort)
|
||||||
|
std::sort(results.begin(), results.end());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!FFlag::DebugLuauToStringNoLexicalSort)
|
||||||
|
std::sort(results.begin(), results.end());
|
||||||
|
}
|
||||||
|
|
||||||
if (optional && results.size() > 1)
|
if (optional && results.size() > 1)
|
||||||
state.emit("(");
|
state.emit("(");
|
||||||
|
@ -953,6 +1023,9 @@ struct TypeStringifier
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::string> results = {};
|
std::vector<std::string> results = {};
|
||||||
|
size_t resultsLength = 0;
|
||||||
|
bool lengthLimitHit = false;
|
||||||
|
|
||||||
for (auto el : uv.parts)
|
for (auto el : uv.parts)
|
||||||
{
|
{
|
||||||
el = follow(el);
|
el = follow(el);
|
||||||
|
@ -969,14 +1042,34 @@ struct TypeStringifier
|
||||||
if (needParens)
|
if (needParens)
|
||||||
state.emit(")");
|
state.emit(")");
|
||||||
|
|
||||||
|
if (FFlag::LuauStringPartLengthLimit)
|
||||||
|
resultsLength += state.result.name.length();
|
||||||
|
|
||||||
results.push_back(std::move(state.result.name));
|
results.push_back(std::move(state.result.name));
|
||||||
|
|
||||||
state.result.name = std::move(saved);
|
state.result.name = std::move(saved);
|
||||||
|
|
||||||
|
if (FFlag::LuauStringPartLengthLimit)
|
||||||
|
{
|
||||||
|
lengthLimitHit = state.opts.maxTypeLength > 0 && resultsLength > state.opts.maxTypeLength;
|
||||||
|
|
||||||
|
if (lengthLimitHit)
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
state.unsee(&uv);
|
state.unsee(&uv);
|
||||||
|
|
||||||
if (!FFlag::DebugLuauToStringNoLexicalSort)
|
if (FFlag::LuauStringPartLengthLimit)
|
||||||
std::sort(results.begin(), results.end());
|
{
|
||||||
|
if (!lengthLimitHit && !FFlag::DebugLuauToStringNoLexicalSort)
|
||||||
|
std::sort(results.begin(), results.end());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!FFlag::DebugLuauToStringNoLexicalSort)
|
||||||
|
std::sort(results.begin(), results.end());
|
||||||
|
}
|
||||||
|
|
||||||
bool first = true;
|
bool first = true;
|
||||||
bool shouldPlaceOnNewlines = results.size() > state.opts.compositeTypesSingleLineLimit || isOverloadedFunction(ty);
|
bool shouldPlaceOnNewlines = results.size() > state.opts.compositeTypesSingleLineLimit || isOverloadedFunction(ty);
|
||||||
|
@ -998,7 +1091,15 @@ struct TypeStringifier
|
||||||
void operator()(TypeId, const ErrorType& tv)
|
void operator()(TypeId, const ErrorType& tv)
|
||||||
{
|
{
|
||||||
state.result.error = true;
|
state.result.error = true;
|
||||||
state.emit("*error-type*");
|
|
||||||
|
if (FFlag::LuauSyntheticErrors && tv.synthetic)
|
||||||
|
{
|
||||||
|
state.emit("*error-type<");
|
||||||
|
stringify(*tv.synthetic);
|
||||||
|
state.emit(">*");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
state.emit("*error-type*");
|
||||||
}
|
}
|
||||||
|
|
||||||
void operator()(TypeId, const LazyType& ltv)
|
void operator()(TypeId, const LazyType& ltv)
|
||||||
|
@ -1173,10 +1274,18 @@ struct TypePackStringifier
|
||||||
state.unsee(&tp);
|
state.unsee(&tp);
|
||||||
}
|
}
|
||||||
|
|
||||||
void operator()(TypePackId, const Unifiable::Error& error)
|
void operator()(TypePackId, const ErrorTypePack& error)
|
||||||
{
|
{
|
||||||
state.result.error = true;
|
state.result.error = true;
|
||||||
state.emit("*error-type*");
|
|
||||||
|
if (FFlag::LuauSyntheticErrors && error.synthetic)
|
||||||
|
{
|
||||||
|
state.emit("*");
|
||||||
|
stringify(*error.synthetic);
|
||||||
|
state.emit("*");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
state.emit("*error-type*");
|
||||||
}
|
}
|
||||||
|
|
||||||
void operator()(TypePackId, const VariadicTypePack& pack)
|
void operator()(TypePackId, const VariadicTypePack& pack)
|
||||||
|
@ -1205,6 +1314,9 @@ struct TypePackStringifier
|
||||||
state.emit(state.getName(tp));
|
state.emit(state.getName(tp));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (FInt::DebugLuauVerboseTypeNames >= 1)
|
||||||
|
state.emit(pack.polarity);
|
||||||
|
|
||||||
if (FInt::DebugLuauVerboseTypeNames >= 2)
|
if (FInt::DebugLuauVerboseTypeNames >= 2)
|
||||||
{
|
{
|
||||||
state.emit("-");
|
state.emit("-");
|
||||||
|
@ -1224,6 +1336,9 @@ struct TypePackStringifier
|
||||||
state.emit("free-");
|
state.emit("free-");
|
||||||
state.emit(state.getName(tp));
|
state.emit(state.getName(tp));
|
||||||
|
|
||||||
|
if (FInt::DebugLuauVerboseTypeNames >= 1)
|
||||||
|
state.emit(pack.polarity);
|
||||||
|
|
||||||
if (FInt::DebugLuauVerboseTypeNames >= 2)
|
if (FInt::DebugLuauVerboseTypeNames >= 2)
|
||||||
{
|
{
|
||||||
state.emit("-");
|
state.emit("-");
|
||||||
|
@ -1848,6 +1963,8 @@ std::string toString(const Constraint& constraint, ToStringOptions& opts)
|
||||||
}
|
}
|
||||||
else if constexpr (std::is_same_v<T, EqualityConstraint>)
|
else if constexpr (std::is_same_v<T, EqualityConstraint>)
|
||||||
return "equality: " + tos(c.resultType) + " ~ " + tos(c.assignmentType);
|
return "equality: " + tos(c.resultType) + " ~ " + tos(c.assignmentType);
|
||||||
|
else if constexpr (std::is_same_v<T, TableCheckConstraint>)
|
||||||
|
return "table_check " + tos(c.expectedType) + " :> " + tos(c.exprType);
|
||||||
else
|
else
|
||||||
static_assert(always_false_v<T>, "Non-exhaustive constraint switch");
|
static_assert(always_false_v<T>, "Non-exhaustive constraint switch");
|
||||||
};
|
};
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue