luau-analyzer-sys 0.1.1

A high-performance, embedded Luau type-checking and analysis engine written in Rust. This crate provides bindings to the Luau analyzer, allowing you to integrate static analysis and code intelligence directly into your applications.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once

#include "Luau/BuiltinTypeFunctions.h"
#include "Luau/Config.h"
#include "Luau/Error.h"
#include "Luau/FileResolver.h"
#include "Luau/Frontend.h"
#include "Luau/IostreamHelpers.h"
#include "Luau/Linter.h"
#include "Luau/Location.h"
#include "Luau/ModuleResolver.h"
#include "Luau/Scope.h"
#include "Luau/ToString.h"
#include "Luau/Type.h"
#include "Luau/TypeFunction.h"

#include "IostreamOptional.h"
#include "ScopedFlags.h"

#include "doctest.h"
#include <string>
#include <string_view>
#include <unordered_map>
#include <optional>
#include <vector>

LUAU_FASTFLAG(DebugLuauFreezeArena)
LUAU_FASTFLAG(DebugLuauForceAllNewSolverTests)
LUAU_FASTFLAG(DebugLuauForceAllOldSolverTests)

LUAU_FASTFLAG(DebugLuauAlwaysShowConstraintSolvingIncomplete);
LUAU_FASTFLAG(DebugLuauForceOldSolver)

#define DOES_NOT_PASS_NEW_SOLVER_GUARD_IMPL(line) ScopedFastFlag sff_##line{FFlag::DebugLuauForceOldSolver, !FFlag::DebugLuauForceAllNewSolverTests};


#define DOES_NOT_PASS_NEW_SOLVER_GUARD() DOES_NOT_PASS_NEW_SOLVER_GUARD_IMPL(__LINE__)


#define DOES_NOT_PASS_OLD_SOLVER_GUARD_IMPL(line) ScopedFastFlag sff_##line{FFlag::DebugLuauForceOldSolver, FFlag::DebugLuauForceAllOldSolverTests};


#define DOES_NOT_PASS_OLD_SOLVER_GUARD() DOES_NOT_PASS_OLD_SOLVER_GUARD_IMPL(__LINE__)




namespace Luau
{

struct TypeChecker;

struct TestRequireNode : RequireNode
{
    TestRequireNode(ModuleName moduleName, std::unordered_map<ModuleName, std::string>* allSources)
        : moduleName(std::move(moduleName))
        , allSources(allSources)
    {
    }

    std::string getLabel() const override;
    std::string getPathComponent() const override;
    std::unique_ptr<RequireNode> resolvePathToNode(const std::string& path) const override;
    std::vector<std::unique_ptr<RequireNode>> getChildren() const override;
    std::vector<RequireAlias> getAvailableAliases() const override;

    ModuleName moduleName;
    std::unordered_map<ModuleName, std::string>* allSources;
};

struct TestFileResolver;
struct TestRequireSuggester : RequireSuggester
{
    TestRequireSuggester(TestFileResolver* resolver)
        : resolver(resolver)
    {
    }

    std::unique_ptr<RequireNode> getNode(const ModuleName& name) const override;
    TestFileResolver* resolver;
};

struct TestFileResolver
    : FileResolver
    , ModuleResolver
{
    TestFileResolver()
        : FileResolver(std::make_shared<TestRequireSuggester>(this))
    {
    }

    std::optional<ModuleInfo> resolveModuleInfo(const ModuleName& currentModuleName, const AstExpr& pathExpr) override;

    const ModulePtr getModule(const ModuleName& moduleName) const override;

    bool moduleExists(const ModuleName& moduleName) const override;

    std::optional<SourceCode> readSource(const ModuleName& name) override;

    std::optional<ModuleInfo> resolveModule(const ModuleInfo* context, AstExpr* expr, const TypeCheckLimits& limits) override;

    std::string getHumanReadableModuleName(const ModuleName& name) const override;

    std::optional<std::string> getEnvironmentForModule(const ModuleName& name) const override;

    std::unordered_map<ModuleName, std::string> source;
    std::unordered_map<ModuleName, SourceCode::Type> sourceTypes;
    std::unordered_map<ModuleName, std::string> environments;
};

struct TestConfigResolver : ConfigResolver
{
    Config defaultConfig;
    std::unordered_map<ModuleName, Config> configFiles;

    const Config& getConfig(const ModuleName& name, const TypeCheckLimits& limits = {}) const override;
};

struct Fixture
{
    explicit Fixture(bool prepareAutocomplete = false);

    explicit Fixture(const Fixture&) = delete;
    Fixture& operator=(const Fixture&) = delete;

    ~Fixture();

    // Throws Luau::ParseErrors if the parse fails.
    AstStatBlock* parse(const std::string& source, const ParseOptions& parseOptions = {});
    CheckResult check(Mode mode, const std::string& source, std::optional<FrontendOptions> = std::nullopt);
    CheckResult check(const std::string& source, std::optional<FrontendOptions> = std::nullopt);

    LintResult lint(const std::string& source, const std::optional<LintOptions>& lintOptions = {});
    LintResult lintModule(const ModuleName& moduleName, const std::optional<LintOptions>& lintOptions = {});

    /// Parse with all language extensions enabled
    ParseResult parseEx(const std::string& source, const ParseOptions& parseOptions = {});
    ParseResult tryParse(const std::string& source, const ParseOptions& parseOptions = {});
    ParseResult matchParseError(const std::string& source, const std::string& message, std::optional<Location> location = std::nullopt);
    // Verify a parse error occurs and the parse error message has the specified prefix
    ParseResult matchParseErrorPrefix(const std::string& source, const std::string& prefix);

    ModulePtr getMainModule(bool forAutocomplete = false);
    SourceModule* getMainSourceModule();

    std::optional<PrimitiveType::Type> getPrimitiveType(TypeId ty);
    std::optional<TypeId> getType(const std::string& name, bool forAutocomplete = false);
    TypeId requireType(const std::string& name);
    TypeId requireType(const ModuleName& moduleName, const std::string& name);
    TypeId requireType(const ModulePtr& module, const std::string& name);
    TypeId requireType(const ScopePtr& scope, const std::string& name);

    std::optional<TypeId> findTypeAtPosition(Position position);
    TypeId requireTypeAtPosition(Position position);
    std::optional<TypeId> findExpectedTypeAtPosition(Position position);

    std::optional<TypeId> lookupType(const std::string& name);
    std::optional<TypeId> lookupImportedType(const std::string& moduleAlias, const std::string& name);
    TypeId requireTypeAlias(const std::string& name);
    TypeId requireExportedType(const ModuleName& moduleName, const std::string& name);

    TypeId parseType(std::string_view src);

    // While most flags can be flipped inside the unit test, some code changes affect the state that is part of Fixture initialization
    // Most often those are changes related to builtin type definitions.
    // In that case, flag can be forced to 'true' using the example below:
    // ScopedFastFlag sff_LuauExampleFlagDefinition{FFlag::LuauExampleFlagDefinition, true};

    // Arena freezing marks the `TypeArena`'s underlying memory as read-only, raising an access violation whenever you mutate it.
    // This is useful for tracking down violations of Luau's memory model.
    ScopedFastFlag sff_DebugLuauFreezeArena{FFlag::DebugLuauFreezeArena, true};

    // This makes sure that errant cases of constraint solving failing to complete still pop up in tests.
    ScopedFastFlag sff_DebugLuauAlwaysShowConstraintSolvingIncomplete{FFlag::DebugLuauAlwaysShowConstraintSolvingIncomplete, true};

    TestFileResolver fileResolver;
    TestConfigResolver configResolver;
    NullModuleResolver moduleResolver;
    std::unique_ptr<SourceModule> sourceModule;
    InternalErrorReporter ice;
    Allocator allocator;
    AstNameTable nameTable{allocator};
    TypeArena arena;

    std::string decorateWithTypes(const std::string& code);

    void dumpErrors(std::ostream& os, const std::vector<TypeError>& errors);

    void dumpErrors(const CheckResult& cr);
    void dumpErrors(const ModulePtr& module);
    void dumpErrors(const Module& module);

    void validateErrors(const std::vector<TypeError>& errors);

    std::string getErrors(const CheckResult& cr);

    void registerTestTypes();

    LoadDefinitionFileResult loadDefinition(const std::string& source, bool forAutocomplete = false);
    // TODO: test theory about dynamic dispatch
    NotNull<BuiltinTypes> getBuiltins();
    const BuiltinTypeFunctions& getBuiltinTypeFunctions();
    virtual Frontend& getFrontend();

    // On platforms that support it, adjust our internal stack guard to
    // limit how much address space we should use before we blow up.  We
    // use this to test the stack guard itself.
    void limitStackSize(size_t size);

private:
    bool hasDumpedErrors = false;

protected:
    bool forAutocomplete = false;
    std::optional<Frontend> frontend;
    BuiltinTypes* builtinTypes = nullptr;

    std::vector<ScopedFastInt> dynamicScopedInts;
};

struct BuiltinsFixture : Fixture
{
    explicit BuiltinsFixture(bool prepareAutocomplete = false);

    // For the purpose of our tests, we're always the latest version of type functions.
    Frontend& getFrontend() override;
};

struct IsSubtypeFixture : Fixture
{
    bool isSubtype(TypeId a, TypeId b);
};

std::optional<std::string> pathExprToModuleName(const ModuleName& currentModuleName, const std::vector<std::string_view>& segments);
std::optional<std::string> pathExprToModuleName(const ModuleName& currentModuleName, const AstExpr& pathExpr);

ModuleName fromString(std::string_view name);

template<typename T>
std::optional<T> get(const std::map<Name, T>& map, const Name& name)
{
    auto it = map.find(name);
    if (it != map.end())
        return std::optional<T>(it->second);
    else
        return std::nullopt;
}

std::string rep(const std::string& s, size_t n);

bool isInArena(TypeId t, const TypeArena& arena);

void dumpErrors(const ModulePtr& module);
void dumpErrors(const Module& module);
void dump(const std::string& name, TypeId ty);
void dump(const std::vector<Constraint>& constraints);

std::optional<TypeId> lookupName(ScopePtr scope, const std::string& name); // Warning: This function runs in O(n**2)

std::optional<TypeId> linearSearchForBinding(Scope* scope, const char* name);

void registerHiddenTypes(Frontend& frontend);
void createSomeExternTypes(Frontend& frontend);

template<typename E>
const E* findError(const CheckResult& result)
{
    for (const auto& e : result.errors)
    {
        if (auto p = get<E>(e))
            return p;
    }

    return nullptr;
}

} // namespace Luau

#define LUAU_REQUIRE_ERRORS(result) \

    do \
    { \
        auto&& r = (result); \
        validateErrors(r.errors); \
        REQUIRE(!r.errors.empty()); \
    } while (false)

#define LUAU_REQUIRE_ERROR_COUNT(count, result) \

    do \
    { \
        auto&& r = (result); \
        validateErrors(r.errors); \
        REQUIRE_MESSAGE(count == r.errors.size(), getErrors(r)); \
    } while (false)

#define LUAU_REQUIRE_NO_ERRORS(result) LUAU_REQUIRE_ERROR_COUNT(0, result)


#define LUAU_CHECK_ERRORS(result) \

    do \
    { \
        auto&& r = (result); \
        validateErrors(r.errors); \
        CHECK(!r.errors.empty()); \
    } while (false)

#define LUAU_CHECK_ERROR_COUNT(count, result) \

    do \
    { \
        auto&& r = (result); \
        validateErrors(r.errors); \
        CHECK_MESSAGE(count == r.errors.size(), getErrors(r)); \
    } while (false)

#define LUAU_CHECK_NO_ERRORS(result) LUAU_CHECK_ERROR_COUNT(0, result)


#define LUAU_CHECK_HAS_KEY(map, key) \

    do \
    { \
        auto&& _m = (map); \
        auto&& _k = (key); \
        const size_t count = _m.count(_k); \
        CHECK_MESSAGE(count, "Map should have key \"" << _k << "\""); \
        if (!count) \
        { \
            MESSAGE("Keys: (count " << _m.size() << ")"); \
            for (const auto& [k, v] : _m) \
            { \
                MESSAGE("\tkey: " << k); \
            } \
        } \
    } while (false)

#define LUAU_CHECK_HAS_NO_KEY(map, key) \

    do \
    { \
        auto&& _m = (map); \
        auto&& _k = (key); \
        const size_t count = _m.count(_k); \
        CHECK_MESSAGE(!count, "Map should not have key \"" << _k << "\""); \
        if (count) \
        { \
            MESSAGE("Keys: (count " << _m.size() << ")"); \
            for (const auto& [k, v] : _m) \
            { \
                MESSAGE("\tkey: " << k); \
            } \
        } \
    } while (false)

#define LUAU_REQUIRE_ERROR(result, Type) \

    do \
    { \
        using T = Type; \
        const auto& res = (result); \
        if (!findError<T>(res)) \
        { \
            dumpErrors(res); \
            REQUIRE_MESSAGE(false, "Expected to find " #Type " error"); \
        } \
    } while (false)

#define LUAU_CHECK_ERROR(result, Type) \

    do \
    { \
        using T = Type; \
        const auto& res = (result); \
        if (!findError<T>(res)) \
        { \
            dumpErrors(res); \
            CHECK_MESSAGE(false, "Expected to find " #Type " error"); \
        } \
    } while (false)

#define LUAU_REQUIRE_NO_ERROR(result, Type) \

    do \
    { \
        using T = Type; \
        const auto& res = (result); \
        if (findError<T>(res)) \
        { \
            dumpErrors(res); \
            REQUIRE_MESSAGE(false, "Expected to find no " #Type " error"); \
        } \
    } while (false)

#define LUAU_CHECK_NO_ERROR(result, Type) \

    do \
    { \
        using T = Type; \
        const auto& res = (result); \
        if (findError<T>(res)) \
        { \
            dumpErrors(res); \
            CHECK_MESSAGE(false, "Expected to find no " #Type " error"); \
        } \
    } while (false)

#define CHECK_LONG_STRINGS_EQ(a, b) \

    do \
    { \
        const auto aa = (a); \
        const auto bb = (b); \
        const auto aLines = split(aa, '\n'); \
        const auto bLines = split(bb, '\n'); \
        CHECK_MESSAGE(aLines.size() == bLines.size(), "Line counts don't match: " << aLines.size() << " != " << bLines.size()); \
        bool anyWrong = false; \
        for (size_t i = 0; i < std::min(aLines.size(), bLines.size()); ++i) \
        { \
            auto aLine = strip(aLines.at(i)); \
            auto bLine = strip(bLines.at(i)); \
            if (aLine != bLine) \
                anyWrong = true; \
            CHECK_MESSAGE(aLine == bLine, "Mismatch on line " << i << " between:\n\t«" << aLine << "»\nand\t«" << bLine << "»\n"); \
        } \
        if (anyWrong) \
        { \
            MESSAGE(aa); \
            MESSAGE(bb); \
        } \
    } while (0)