22#pragma warning(push, 0)
25#include "Luau/BuiltinDefinitions.h"
26#include "Luau/Config.h"
27#include "Luau/ConfigResolver.h"
28#include "Luau/Error.h"
29#include "Luau/FileResolver.h"
30#include "Luau/Frontend.h"
31#include "Luau/ToString.h"
33#include "Luau/TypeArena.h"
34#include "Luau/TypePack.h"
45 constexpr std::string_view
50 constexpr const char*
HIDDEN_GLOBALS[] = {
"os",
"debug",
"gcinfo",
"getfenv",
51 "setfenv",
"newproxy",
"loadstring",
"collectgarbage"};
54 const std::u8string text = path.generic_u8string();
56 return {text.begin(), text.end()};
66 for (
const auto& [path, source] : package.
files)
70 void AddModule(std::string name, std::string source) {
71 m_sources.insert_or_assign(std::move(name), std::move(source));
74 std::optional<Luau::SourceCode>
readSource(
const Luau::ModuleName& name)
override {
79 return Luau::SourceCode{found->second, Luau::SourceCode::Module};
84 std::optional<Luau::ModuleInfo>
resolveModule(
const Luau::ModuleInfo* context, Luau::AstExpr* expr,
85 const Luau::TypeCheckLimits& )
override {
86 const auto* constant = expr ? expr->as<Luau::AstExprConstantString>() :
nullptr;
87 if (context ==
nullptr || constant ==
nullptr)
90 const std::string_view name(constant->value.data, constant->value.size);
93 return Luau::ModuleInfo{
m_package +
"/" + (path ? *path : std::string(name))};
107 const Luau::TypeCheckLimits& )
const override {
117 std::string
KindText(
const Luau::TypeError& error) {
118 if (Luau::get_if<Luau::SyntaxError>(&error.data))
120 if (Luau::get_if<Luau::UnknownSymbol>(&error.data))
122 if (Luau::get_if<Luau::UnknownProperty>(&error.data))
124 if (Luau::get_if<Luau::MissingProperties>(&error.data))
126 if (Luau::get_if<Luau::TypeMismatch>(&error.data))
128 if (Luau::get_if<Luau::UnknownRequire>(&error.data))
129 return "require の先が無い";
130 if (Luau::get_if<Luau::CountMismatch>(&error.data))
143 Luau::Location
ReturnLocation(Luau::Frontend& frontend,
const std::string& moduleName) {
144 Luau::Location location;
145 const Luau::SourceModule* source = frontend.getSourceModule(moduleName);
146 if (source ==
nullptr || source->root ==
nullptr)
149 for (Luau::AstStat* statement : source->root->body) {
150 if (statement->is<Luau::AstStatReturn>())
151 location = statement->location;
161 for (
const char character : text) {
162 const bool isSpace = character ==
'\n' || character ==
'\r' || character ==
'\t' || character ==
' ';
164 space = !line.empty();
178 std::string_view message) {
180 .line = location.begin.line + 1,
181 .column = location.begin.column + 1,
182 .kind = std::move(kind),
189 Luau::ToStringOptions options;
190 options.exhaustive =
true;
192 return Luau::toString(type, options);
196 const auto* primitive = Luau::get<Luau::PrimitiveType>(Luau::follow(type));
198 return primitive !=
nullptr && primitive->type == Luau::PrimitiveType::NilType;
202 const auto* union_ = Luau::get<Luau::UnionType>(Luau::follow(type));
204 return union_ !=
nullptr && std::ranges::any_of(union_->options,
IsNil);
209 type = Luau::follow(type);
210 const auto* union_ = Luau::get<Luau::UnionType>(type);
211 if (union_ ==
nullptr)
214 std::vector<Luau::TypeId> options;
215 for (Luau::TypeId option : union_->options) {
217 options.push_back(option);
220 return options.size() == 1 ? Luau::follow(options[0]) : type;
224 std::string text =
"[\"";
225 for (
const char character : key) {
226 if (character ==
'"' || character ==
'\\')
256 void Walk(Luau::TypeId expected, Luau::TypeId actual,
const Place& place,
int depth) {
258 actual = Luau::follow(actual);
259 if (Luau::get<Luau::AnyType>(expected))
262 const auto* expectedTable = Luau::get<Luau::TableType>(expected);
263 const auto* actualTable = Luau::get<Luau::TableType>(actual);
264 if (expectedTable ==
nullptr || actualTable ==
nullptr || actualTable->indexer || depth >
MAX_DEPTH) {
274 const std::string tableModule = actualTable->definitionModuleName.empty()
276 : actualTable->definitionModuleName;
278 WalkExtraFields(*expectedTable, *actualTable, place, tableModule, depth);
288 return place.
path.empty() ? name : place.
path +
"." + name;
291 void WalkProperty(Luau::TypeId expected,
const std::string& name,
const Luau::Property& property,
292 const Place& parent,
const std::string& tableModule,
int depth) {
293 if (!property.readTy)
298 .module = tableModule,
299 .location =
property.location.value_or(parent.
location)};
300 Walk(expected, *property.readTy, child, depth + 1);
305 const std::string& tableModule,
int depth) {
306 for (
const auto& [name, property] : expected.props) {
307 if (!property.readTy)
310 const auto found = actual.props.find(name);
311 if (found != actual.props.end()) {
312 WalkProperty(*property.readTy, name, found->second, place, tableModule, depth);
319 std::format(
"{} に {} が無い(型 {})", place.
path.empty() ?
"返す表" : place.
path, name,
326 const std::string& tableModule,
int depth) {
327 for (
const auto& [name, property] : actual.props) {
328 if (expected.props.contains(name))
331 if (expected.indexer) {
332 WalkProperty(expected.indexer->indexResultType, name, property, place, tableModule, depth);
338 std::format(
"{} は型の定義に無い欄(綴りの誤り?)",
ChildPath(place, name))));
346 std::string
ShapeCheckSource(
const std::string& requireName,
const std::vector<ShapeWalker::Leaf>& leaves) {
347 std::string source = std::format(
"--!strict\nlocal value = require(\"{}\")\n", requireName);
348 for (
size_t index = 0; index < leaves.size(); ++index)
349 source += std::format(
"local _{}: {} = value{}\n", index, leaves[index].typeText,
350 leaves[index].expression);
352 return source +
"return nil\n";
357 const Luau::AstName astName = globals.globalNames.names->get(name);
358 if (astName.value !=
nullptr)
359 globals.globalScope->bindings.erase(Luau::Symbol(astName));
365 for (
const Luau::ParseError& error : loaded.parseResult.errors)
366 text += std::format(
" {}:{}: {}", error.getLocation().begin.line + 1,
367 error.getLocation().begin.column + 1, error.getMessage());
369 for (
const Luau::TypeError& error : loaded.module->errors)
370 text += std::format(
" {}:{}: {}", error.location.begin.line + 1, error.location.begin.column + 1,
371 Luau::toString(error));
385 std::vector<TypeDiagnostic>
Check(
const PackageSource& package,
const std::vector<std::string>& files,
386 const std::optional<ReturnCheck>& returnCheck);
390 std::set<TypeDiagnostic>& found);
394 const std::vector<std::string>& files,
395 const std::optional<ReturnCheck>& returnCheck) {
401 std::set<TypeDiagnostic> found;
402 bool targetHasSyntaxError =
false;
403 for (
const std::string& file : files) {
404 const Luau::CheckResult result =
frontend.check(package.
name +
"/" + file);
405 for (
const Luau::TypeError& error : result.errors) {
407 if (Luau::get_if<Luau::ExplicitFunctionAnnotationRecommended>(&error.data))
410 if (returnCheck && error.moduleName == returnCheck->targetModule &&
411 Luau::get_if<Luau::SyntaxError>(&error.data))
412 targetHasSyntaxError =
true;
414 found.insert(MakeDiagnostic(error.moduleName, error.location, KindText(error),
415 Luau::toString(error, Luau::TypeErrorToStringOptions{&
fileResolver})));
420 if (returnCheck && !targetHasSyntaxError)
423 return {found.begin(), found.end()};
427 std::set<TypeDiagnostic>& found) {
428 const Luau::ModulePtr module =
frontend.moduleResolver.getModule(returnCheck.targetModule);
429 const auto expectedType =
frontend.globals.globalScope->lookupType(returnCheck.typeName);
430 if (!module || !expectedType)
433 const Luau::Location returnLocation = ReturnLocation(
frontend, returnCheck.targetModule);
434 const std::optional<Luau::TypeId> actual = Luau::first(module->returnType);
436 found.insert(MakeDiagnostic(returnCheck.targetModule, returnLocation,
"返す表が無い",
437 std::format(
"{} の形の表を 1 つ返す", returnCheck.typeName)));
443 walker.Walk(expectedType->type, *actual,
445 .expression =
"", .path =
"", .module = returnCheck.targetModule, .location = returnLocation},
447 found.insert(walker.diagnostics.begin(), walker.diagnostics.end());
448 if (walker.leaves.empty())
451 std::string requireName = returnCheck.targetModule.substr(package.
name.size() + 1);
452 requireName.resize(requireName.size() - MODULE_EXTENSION.size());
453 const std::string shapeModule = package.
name +
"/" + std::string(RETURN_CHECK_MODULE);
454 fileResolver.AddModule(shapeModule, ShapeCheckSource(requireName, walker.leaves));
457 const Luau::CheckResult result =
frontend.check(shapeModule);
458 for (
const Luau::TypeError& error : result.errors) {
459 if (error.moduleName != shapeModule)
462 const std::string message = Luau::toString(error, Luau::TypeErrorToStringOptions{&
fileResolver});
463 const uint32_t line = error.location.begin.line;
464 if (line >= FIRST_LEAF_LINE && line - FIRST_LEAF_LINE < walker.leaves.size()) {
465 const ShapeWalker::Leaf& leaf = walker.leaves[line - FIRST_LEAF_LINE];
466 found.insert(MakeDiagnostic(leaf.module, leaf.location, KindText(error),
467 std::format(
"{}: {}", leaf.path, message)));
471 found.insert(MakeDiagnostic(returnCheck.targetModule, returnLocation,
472 std::format(
"返す表が {} の形でない", returnCheck.typeName), message));
481 auto impl = std::make_unique<Impl>();
482 Luau::Frontend& frontend = impl->frontend;
485 Luau::registerBuiltinGlobals(frontend, frontend.globals);
486 HideGlobals(frontend.globals);
488 const Luau::LoadDefinitionFileResult loaded = frontend.loadDefinitionFile(
489 frontend.globals, frontend.globals.globalScope, definitions, std::string(DEFINITIONS_PACKAGE),
false,
491 Luau::freeze(frontend.globals.globalTypes);
493 return std::unexpected(std::format(
"型の定義に誤りがある:{}", DefinitionErrors(loaded)));
496 if (!frontend.globals.globalScope->lookupType(std::string(name)))
497 return std::unexpected(std::format(
"型の定義に export type {} が無い", name));
500 return std::unique_ptr<LuauTypeChecker>(
new LuauTypeChecker(std::move(impl)));
504 if (!package.
files.contains(std::string(MANIFEST_FILE)))
507 const std::string manifest = package.
name +
"/" + std::string(MANIFEST_FILE);
509 return m_impl->Check(package, {std::string(MANIFEST_FILE)},
514 std::vector<std::string> files;
515 for (
const auto& [path, source] : package.
files)
516 files.push_back(path);
519 std::optional<ReturnCheck> returnCheck;
521 if (entryPath && package.
files.contains(*entryPath))
522 returnCheck = ReturnCheck{.targetModule = package.
name +
"/" + *entryPath,
525 return m_impl->Check(package, files, returnCheck);
531 return std::format(
"{}:{}:{}: {}: {}", diagnostic.
file, diagnostic.
line, diagnostic.
column, diagnostic.
kind,
537 for (
size_t index = 0; index < diagnostics.size() && index < maxShown; ++index) {
543 if (diagnostics.size() > maxShown)
544 text += std::format(
"(ほか {} 件)", diagnostics.size() - maxShown);
556 const fs::path& definitionsFile) {
557 std::ifstream file(definitionsFile, std::ios::binary);
559 return std::unexpected(std::format(
"型の定義 {} を開けない", ToUtf8String(definitionsFile)));
561 const std::string source{std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>()};
564 return std::unexpected(std::format(
"{}: {}", ToUtf8String(definitionsFile), checker.error()));
LuauTypeChecker(const LuauTypeChecker &)=delete
static std::expected< std::unique_ptr< LuauTypeChecker >, std::string > Create(std::string_view definitions)
std::vector< TypeDiagnostic > CheckPackage(const PackageSource &package, std::string_view entry)
std::unique_ptr< Impl > m_impl
std::vector< TypeDiagnostic > CheckManifest(const PackageSource &package)
std::optional< Luau::SourceCode > readSource(const Luau::ModuleName &name) override
std::optional< Luau::ModuleInfo > resolveModule(const Luau::ModuleInfo *context, Luau::AstExpr *expr, const Luau::TypeCheckLimits &) override
std::map< std::string, std::string > m_sources
void Reset(const PackageSource &package)
void AddModule(std::string name, std::string source)
static constexpr int MAX_DEPTH
std::vector< Leaf > leaves
std::vector< TypeDiagnostic > diagnostics
void Walk(Luau::TypeId expected, Luau::TypeId actual, const Place &place, int depth)
void WalkExtraFields(const Luau::TableType &expected, const Luau::TableType &actual, const Place &place, const std::string &tableModule, int depth)
void WalkProperty(Luau::TypeId expected, const std::string &name, const Luau::Property &property, const Place &parent, const std::string &tableModule, int depth)
static std::string ChildPath(const Place &place, const std::string &name)
void WalkExpectedFields(const Luau::TableType &expected, const Luau::TableType &actual, const Place &place, const std::string &tableModule, int depth)
const Luau::Config & getConfig(const Luau::ModuleName &, const Luau::TypeCheckLimits &) const override
constexpr uint32_t FIRST_LEAF_LINE
bool IsNil(Luau::TypeId type)
std::string OneLine(std::string_view text)
constexpr const char * HIDDEN_GLOBALS[]
constexpr std::string_view MODULE_EXTENSION
constexpr std::string_view DEFINITIONS_PACKAGE
constexpr std::string_view MANIFEST_FILE
std::string ShapeCheckSource(const std::string &requireName, const std::vector< ShapeWalker::Leaf > &leaves)
std::string ToUtf8String(const fs::path &path)
std::string TypeText(Luau::TypeId type)
std::string KindText(const Luau::TypeError &error)
Luau::Location ReturnLocation(Luau::Frontend &frontend, const std::string &moduleName)
Luau::TypeId StripOptional(Luau::TypeId type)
void HideGlobals(Luau::GlobalTypes &globals)
std::string DefinitionErrors(const Luau::LoadDefinitionFileResult &loaded)
std::string QuoteKey(std::string_view key)
constexpr std::string_view RETURN_CHECK_MODULE
TypeDiagnostic MakeDiagnostic(std::string file, const Luau::Location &location, std::string kind, std::string_view message)
bool IsOptional(Luau::TypeId type)
std::expected< std::string, std::string > PackageModulePath(std::string_view name)
std::string SummarizeDiagnostics(const std::vector< TypeDiagnostic > &diagnostics, size_t maxShown)
std::string FormatDiagnostic(const TypeDiagnostic &diagnostic)
fs::path DefaultTypeDefinitionsPath()
std::expected< std::unique_ptr< LuauTypeChecker >, std::string > CreateTypeCheckerFromFile(const fs::path &definitionsFile)
constexpr std::string_view ENTRY_TYPE_NAME
constexpr std::string_view MANIFEST_TYPE_NAME
fs::path ExecutableDirectory()
void CheckReturnShape(const PackageSource &package, const ReturnCheck &returnCheck, std::set< TypeDiagnostic > &found)
std::vector< TypeDiagnostic > Check(const PackageSource &package, const std::vector< std::string > &files, const std::optional< ReturnCheck > &returnCheck)
StrictConfigResolver configResolver
PackageFileResolver fileResolver
std::map< std::string, std::string > files