#include "Luau/AstQuery.h"
#include "Luau/Frontend.h"
#include "Luau/Module.h"
#include "Luau/Scope.h"
#include "Luau/TypeInfer.h"
#include "Luau/Type.h"
#include "Luau/ToString.h"
#include "Luau/Common.h"
#include <algorithm>
namespace Luau
{
namespace
{
struct AutocompleteNodeFinder : public AstVisitor
{
const Position pos;
std::vector<AstNode*> ancestry;
explicit AutocompleteNodeFinder(Position pos, AstNode* root)
: pos(pos)
{
}
bool visit(AstExpr* expr) override
{
if (expr->location.begin <= pos && pos <= expr->location.end && expr->location.begin != expr->location.end)
{
ancestry.push_back(expr);
return true;
}
return false;
}
bool visit(AstStat* stat) override
{
if (stat->location.begin < pos && (stat->hasSemicolon ? pos < stat->location.end : pos <= stat->location.end))
{
ancestry.push_back(stat);
return true;
}
return false;
}
bool visit(AstType* type) override
{
if (type->location.begin < pos && pos <= type->location.end)
{
ancestry.push_back(type);
return true;
}
return false;
}
bool visit(AstTypeError* type) override
{
if (type->isMissing && type->location.containsClosed(pos))
{
ancestry.push_back(type);
return true;
}
return false;
}
bool visit(class AstTypePack* typePack) override
{
return true;
}
bool visit(AstStatBlock* block) override
{
if (ancestry.empty())
{
ancestry.push_back(block);
return true;
}
if (!ancestry.empty() && ancestry.back()->is<AstExprIndexName>())
return false;
if (!ancestry.empty() && ancestry.back()->is<AstTypeError>())
return false;
if (block->location.begin == pos && !ancestry.empty())
{
if (ancestry.back()->asExpr() && !ancestry.back()->is<AstExprFunction>())
return false;
if (ancestry.back()->asType())
return false;
}
if (block->location.begin <= pos && pos <= block->location.end)
{
ancestry.push_back(block);
return true;
}
return false;
}
};
struct FindNode : public AstVisitor
{
const Position pos;
const Position documentEnd;
AstNode* best = nullptr;
explicit FindNode(Position pos, Position documentEnd)
: pos(pos)
, documentEnd(documentEnd)
{
}
bool visit(AstNode* node) override
{
if (node->location.contains(pos))
{
best = node;
return true;
}
if (node->location.end == documentEnd && pos >= documentEnd)
{
best = node;
return true;
}
return false;
}
bool visit(AstStatFunction* node) override
{
visit(static_cast<AstNode*>(node));
if (node->name->location.contains(pos))
node->name->visit(this);
else if (node->func->location.contains(pos))
node->func->visit(this);
return false;
}
bool visit(AstStatBlock* block) override
{
visit(static_cast<AstNode*>(block));
for (AstStat* stat : block->body)
{
if (stat->location.end < pos)
continue;
if (stat->location.begin > pos)
break;
stat->visit(this);
}
return false;
}
};
}
FindFullAncestry::FindFullAncestry(Position pos, Position documentEnd, bool includeTypes)
: pos(pos)
, documentEnd(documentEnd)
, includeTypes(includeTypes)
{
}
bool FindFullAncestry::visit(AstType* type)
{
if (includeTypes)
return visit(static_cast<AstNode*>(type));
else
return false;
}
bool FindFullAncestry::visit(AstStatFunction* node)
{
visit(static_cast<AstNode*>(node));
if (node->name->location.contains(pos))
node->name->visit(this);
else if (node->func->location.contains(pos))
node->func->visit(this);
return false;
}
bool FindFullAncestry::visit(AstNode* node)
{
if (node->location.contains(pos))
{
nodes.push_back(node);
return true;
}
if (node->location.end == documentEnd && pos >= documentEnd)
{
nodes.push_back(node);
return true;
}
return false;
}
std::vector<AstNode*> findAncestryAtPositionForAutocomplete(const SourceModule& source, Position pos)
{
return findAncestryAtPositionForAutocomplete(source.root, pos);
}
std::vector<AstNode*> findAncestryAtPositionForAutocomplete(AstStatBlock* root, Position pos)
{
AutocompleteNodeFinder finder{pos, root};
root->visit(&finder);
return finder.ancestry;
}
std::vector<AstNode*> findAstAncestryOfPosition(const SourceModule& source, Position pos, bool includeTypes)
{
return findAstAncestryOfPosition(source.root, pos, includeTypes);
}
std::vector<AstNode*> findAstAncestryOfPosition(AstStatBlock* root, Position pos, bool includeTypes)
{
const Position end = root->location.end;
if (pos > end)
pos = end;
FindFullAncestry finder(pos, end, includeTypes);
root->visit(&finder);
return finder.nodes;
}
AstNode* findNodeAtPosition(const SourceModule& source, Position pos)
{
return findNodeAtPosition(source.root, pos);
}
AstNode* findNodeAtPosition(AstStatBlock* root, Position pos)
{
const Position end = root->location.end;
if (pos < root->location.begin)
return root;
if (pos > end)
pos = end;
FindNode findNode{pos, end};
findNode.visit(root);
return findNode.best;
}
AstExpr* findExprAtPosition(const SourceModule& source, Position pos)
{
AstNode* node = findNodeAtPosition(source, pos);
if (node)
return node->asExpr();
else
return nullptr;
}
ScopePtr findScopeAtPosition(const Module& module, Position pos)
{
if (module.scopes.empty())
return nullptr;
Location scopeLocation = module.scopes.front().first;
ScopePtr scope = module.scopes.front().second;
for (const auto& s : module.scopes)
{
if (s.first.contains(pos))
{
if (!scope || scopeLocation.encloses(s.first))
{
scopeLocation = s.first;
scope = s.second;
}
}
}
return scope;
}
std::optional<TypeId> findTypeAtPosition(const Module& module, const SourceModule& sourceModule, Position pos)
{
if (auto expr = findExprAtPosition(sourceModule, pos))
{
if (auto it = module.astTypes.find(expr))
return *it;
}
return std::nullopt;
}
std::optional<TypeId> findExpectedTypeAtPosition(const Module& module, const SourceModule& sourceModule, Position pos)
{
if (auto expr = findExprAtPosition(sourceModule, pos))
{
if (auto it = module.astExpectedTypes.find(expr))
return *it;
}
return std::nullopt;
}
static std::optional<AstStatLocal*> findBindingLocalStatement(const SourceModule& source, const Binding& binding)
{
if (binding.location == Location{{0, 0}, {0, 0}})
return std::nullopt;
std::vector<AstNode*> nodes = findAstAncestryOfPosition(source, binding.location.begin);
auto iter = std::find_if(
nodes.rbegin(),
nodes.rend(),
[](AstNode* node)
{
return node->is<AstStatLocal>();
}
);
return iter != nodes.rend() ? std::make_optional((*iter)->as<AstStatLocal>()) : std::nullopt;
}
std::optional<Binding> findBindingAtPosition(const Module& module, const SourceModule& source, Position pos)
{
ExprOrLocal exprOrLocal = findExprOrLocalAtPosition(source, pos);
Symbol name;
if (auto expr = exprOrLocal.getExpr())
{
if (auto g = expr->as<AstExprGlobal>())
name = g->name;
else if (auto l = expr->as<AstExprLocal>())
name = l->local;
else
return std::nullopt;
}
else if (auto local = exprOrLocal.getLocal())
name = local;
else
return std::nullopt;
ScopePtr currentScope = findScopeAtPosition(module, pos);
while (currentScope)
{
auto iter = currentScope->bindings.find(name);
if (iter != currentScope->bindings.end() && iter->second.location.begin <= pos)
{
std::optional<AstStatLocal*> bindingStatement = findBindingLocalStatement(source, iter->second);
if (!bindingStatement || !(*bindingStatement)->location.contains(pos))
return iter->second;
}
currentScope = currentScope->parent;
}
return std::nullopt;
}
namespace
{
struct FindExprOrLocal : public AstVisitor
{
const Position pos;
ExprOrLocal result;
explicit FindExprOrLocal(Position pos)
: pos(pos)
{
}
bool isCloserMatch(Location newLocation)
{
auto current = result.getLocation();
return newLocation.contains(pos) && (!current || current->encloses(newLocation));
}
bool visit(AstStatBlock* block) override
{
for (AstStat* stat : block->body)
{
if (stat->location.end <= pos)
continue;
if (stat->location.begin > pos)
break;
stat->visit(this);
}
return false;
}
bool visit(AstExpr* expr) override
{
if (isCloserMatch(expr->location))
{
result.setExpr(expr);
return true;
}
return false;
}
bool visitLocal(AstLocal* local)
{
if (isCloserMatch(local->location))
{
result.setLocal(local);
return true;
}
return false;
}
bool visit(AstStatLocalFunction* function) override
{
visitLocal(function->name);
return true;
}
bool visit(AstStatLocal* al) override
{
for (size_t i = 0; i < al->vars.size; ++i)
{
visitLocal(al->vars.data[i]);
}
return true;
}
bool visit(AstExprFunction* fn) override
{
for (size_t i = 0; i < fn->args.size; ++i)
{
visitLocal(fn->args.data[i]);
}
return visit((class AstExpr*)fn);
}
bool visit(AstStatFor* forStat) override
{
visitLocal(forStat->var);
return true;
}
bool visit(AstStatForIn* forIn) override
{
for (AstLocal* var : forIn->vars)
{
visitLocal(var);
}
return true;
}
};
};
ExprOrLocal findExprOrLocalAtPosition(const SourceModule& source, Position pos)
{
FindExprOrLocal findVisitor{pos};
findVisitor.visit(source.root);
return findVisitor.result;
}
static std::optional<DocumentationSymbol> checkOverloadedDocumentationSymbol(
const Module& module,
const TypeId ty,
const AstExpr* parentExpr,
std::optional<DocumentationSymbol> documentationSymbol
)
{
if (!documentationSymbol)
return std::nullopt;
if (get<IntersectionType>(follow(ty)))
{
TypeId matchingOverload = nullptr;
if (parentExpr && parentExpr->is<AstExprCall>())
{
if (auto it = module.astOverloadResolvedTypes.find(parentExpr))
{
matchingOverload = *it;
}
}
if (matchingOverload)
{
std::string overloadSymbol = *documentationSymbol + "/overload/";
overloadSymbol += toString(matchingOverload);
return overloadSymbol;
}
}
return documentationSymbol;
}
static std::optional<DocumentationSymbol> getMetatableDocumentation(
const Module& module,
AstExpr* parentExpr,
const TableType* mtable,
const AstName& index
)
{
auto indexIt = mtable->props.find("__index");
if (indexIt == mtable->props.end())
return std::nullopt;
TypeId followed;
if (indexIt->second.readTy)
followed = follow(*indexIt->second.readTy);
else if (indexIt->second.writeTy)
followed = follow(*indexIt->second.writeTy);
else
return std::nullopt;
const TableType* ttv = get<TableType>(followed);
if (!ttv)
return std::nullopt;
auto propIt = ttv->props.find(index.value);
if (propIt == ttv->props.end())
return std::nullopt;
if (auto ty = propIt->second.readTy)
return checkOverloadedDocumentationSymbol(module, *ty, parentExpr, propIt->second.documentationSymbol);
return std::nullopt;
}
std::optional<DocumentationSymbol> getDocumentationSymbolAtPosition(const SourceModule& source, const Module& module, Position position)
{
std::vector<AstNode*> ancestry = findAstAncestryOfPosition(source, position);
AstExpr* targetExpr = ancestry.size() >= 1 ? ancestry[ancestry.size() - 1]->asExpr() : nullptr;
AstExpr* parentExpr = ancestry.size() >= 2 ? ancestry[ancestry.size() - 2]->asExpr() : nullptr;
if (targetExpr)
{
if (AstExprIndexName* indexName = targetExpr->as<AstExprIndexName>())
{
if (auto it = module.astTypes.find(indexName->expr))
{
TypeId parentTy = follow(*it);
if (const TableType* ttv = get<TableType>(parentTy))
{
if (auto propIt = ttv->props.find(indexName->index.value); propIt != ttv->props.end())
{
if (auto ty = propIt->second.readTy)
return checkOverloadedDocumentationSymbol(module, *ty, parentExpr, propIt->second.documentationSymbol);
}
}
else if (const ExternType* etv = get<ExternType>(parentTy))
{
while (etv)
{
if (auto propIt = etv->props.find(indexName->index.value); propIt != etv->props.end())
{
if (auto ty = propIt->second.readTy)
return checkOverloadedDocumentationSymbol(module, *ty, parentExpr, propIt->second.documentationSymbol);
}
etv = etv->parent ? Luau::get<Luau::ExternType>(*etv->parent) : nullptr;
}
}
else if (const PrimitiveType* ptv = get<PrimitiveType>(parentTy); ptv && ptv->metatable)
{
if (auto mtable = get<TableType>(*ptv->metatable))
{
if (std::optional<std::string> docSymbol = getMetatableDocumentation(module, parentExpr, mtable, indexName->index))
return docSymbol;
}
}
}
}
else if (AstExprFunction* fn = targetExpr->as<AstExprFunction>())
{
if (parentExpr && parentExpr->is<AstExprCall>())
{
AstExprCall* call = parentExpr->as<AstExprCall>();
if (std::optional<DocumentationSymbol> parentSymbol = getDocumentationSymbolAtPosition(source, module, call->func->location.begin))
{
for (size_t i = 0; i < call->args.size; ++i)
{
AstExpr* callArg = call->args.data[i];
if (callArg == targetExpr)
{
std::string fnSymbol = *parentSymbol + "/param/" + std::to_string(i);
for (size_t j = 0; j < fn->args.size; ++j)
{
AstLocal* fnArg = fn->args.data[j];
if (fnArg->location.contains(position))
{
return fnSymbol + "/param/" + std::to_string(j);
}
}
}
}
}
}
}
}
if (std::optional<Binding> binding = findBindingAtPosition(module, source, position))
return checkOverloadedDocumentationSymbol(module, binding->typeId, parentExpr, binding->documentationSymbol);
if (std::optional<TypeId> ty = findTypeAtPosition(module, source, position))
{
if ((*ty)->documentationSymbol)
{
return (*ty)->documentationSymbol;
}
}
return std::nullopt;
}
}