2021-10-29 13:25:12 -07:00
|
|
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
|
|
|
#include "Luau/Autocomplete.h"
|
|
|
|
|
|
|
|
#include "Luau/AstQuery.h"
|
|
|
|
#include "Luau/BuiltinDefinitions.h"
|
|
|
|
#include "Luau/Frontend.h"
|
|
|
|
#include "Luau/ToString.h"
|
2023-11-03 16:45:04 -07:00
|
|
|
#include "Luau/Subtyping.h"
|
2021-10-29 13:25:12 -07:00
|
|
|
#include "Luau/TypeInfer.h"
|
|
|
|
#include "Luau/TypePack.h"
|
|
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
#include <unordered_set>
|
|
|
|
#include <utility>
|
|
|
|
|
2023-11-03 16:45:04 -07:00
|
|
|
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
2023-10-13 13:20:12 -07:00
|
|
|
LUAU_FASTFLAG(DebugLuauReadWriteProperties);
|
2023-08-25 10:23:55 -07:00
|
|
|
LUAU_FASTFLAGVARIABLE(LuauAutocompleteStringLiteralBounds, false);
|
2023-05-26 00:36:34 +03:00
|
|
|
|
2021-10-29 13:25:12 -07:00
|
|
|
static const std::unordered_set<std::string> kStatementStartingKeywords = {
|
|
|
|
"while", "if", "local", "repeat", "function", "do", "for", "return", "break", "continue", "type", "export"};
|
|
|
|
|
|
|
|
namespace Luau
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
static bool alreadyHasParens(const std::vector<AstNode*>& nodes)
|
|
|
|
{
|
|
|
|
auto iter = nodes.rbegin();
|
|
|
|
while (iter != nodes.rend() &&
|
|
|
|
((*iter)->is<AstExprLocal>() || (*iter)->is<AstExprGlobal>() || (*iter)->is<AstExprIndexName>() || (*iter)->is<AstExprIndexExpr>()))
|
|
|
|
{
|
|
|
|
iter++;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (iter == nodes.rend() || iter == nodes.rbegin())
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (AstExprCall* call = (*iter)->as<AstExprCall>())
|
|
|
|
{
|
|
|
|
return call->func == *(iter - 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2023-01-04 12:53:17 -08:00
|
|
|
static ParenthesesRecommendation getParenRecommendationForFunc(const FunctionType* func, const std::vector<AstNode*>& nodes)
|
2021-10-29 13:25:12 -07:00
|
|
|
{
|
|
|
|
if (alreadyHasParens(nodes))
|
|
|
|
{
|
|
|
|
return ParenthesesRecommendation::None;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto idxExpr = nodes.back()->as<AstExprIndexName>();
|
|
|
|
bool hasImplicitSelf = idxExpr && idxExpr->op == ':';
|
2022-04-14 16:57:43 -07:00
|
|
|
auto [argTypes, argVariadicPack] = Luau::flatten(func->argTypes);
|
|
|
|
|
|
|
|
if (argVariadicPack.has_value() && isVariadic(*argVariadicPack))
|
|
|
|
return ParenthesesRecommendation::CursorInside;
|
|
|
|
|
|
|
|
bool noArgFunction = argTypes.empty() || (hasImplicitSelf && argTypes.size() == 1);
|
2021-10-29 13:25:12 -07:00
|
|
|
return noArgFunction ? ParenthesesRecommendation::CursorAfter : ParenthesesRecommendation::CursorInside;
|
|
|
|
}
|
|
|
|
|
2023-01-04 12:53:17 -08:00
|
|
|
static ParenthesesRecommendation getParenRecommendationForIntersect(const IntersectionType* intersect, const std::vector<AstNode*>& nodes)
|
2021-10-29 13:25:12 -07:00
|
|
|
{
|
|
|
|
ParenthesesRecommendation rec = ParenthesesRecommendation::None;
|
|
|
|
for (Luau::TypeId partId : intersect->parts)
|
|
|
|
{
|
2023-01-04 12:53:17 -08:00
|
|
|
if (auto partFunc = Luau::get<FunctionType>(partId))
|
2021-10-29 13:25:12 -07:00
|
|
|
{
|
|
|
|
rec = std::max(rec, getParenRecommendationForFunc(partFunc, nodes));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return ParenthesesRecommendation::None;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return rec;
|
|
|
|
}
|
|
|
|
|
|
|
|
static ParenthesesRecommendation getParenRecommendation(TypeId id, const std::vector<AstNode*>& nodes, TypeCorrectKind typeCorrect)
|
|
|
|
{
|
|
|
|
// If element is already type-correct, even a function should be inserted without parenthesis
|
|
|
|
if (typeCorrect == TypeCorrectKind::Correct)
|
|
|
|
return ParenthesesRecommendation::None;
|
|
|
|
|
|
|
|
id = Luau::follow(id);
|
2023-01-04 12:53:17 -08:00
|
|
|
if (auto func = get<FunctionType>(id))
|
2021-10-29 13:25:12 -07:00
|
|
|
{
|
|
|
|
return getParenRecommendationForFunc(func, nodes);
|
|
|
|
}
|
2023-01-04 12:53:17 -08:00
|
|
|
else if (auto intersect = get<IntersectionType>(id))
|
2021-10-29 13:25:12 -07:00
|
|
|
{
|
|
|
|
return getParenRecommendationForIntersect(intersect, nodes);
|
|
|
|
}
|
|
|
|
return ParenthesesRecommendation::None;
|
|
|
|
}
|
|
|
|
|
2021-12-10 14:05:05 -08:00
|
|
|
static std::optional<TypeId> findExpectedTypeAt(const Module& module, AstNode* node, Position position)
|
|
|
|
{
|
|
|
|
auto expr = node->asExpr();
|
|
|
|
if (!expr)
|
|
|
|
return std::nullopt;
|
|
|
|
|
|
|
|
// Extra care for first function call argument location
|
|
|
|
// When we don't have anything inside () yet, we also don't have an AST node to base our lookup
|
|
|
|
if (AstExprCall* exprCall = expr->as<AstExprCall>())
|
|
|
|
{
|
|
|
|
if (exprCall->args.size == 0 && exprCall->argLocation.contains(position))
|
|
|
|
{
|
|
|
|
auto it = module.astTypes.find(exprCall->func);
|
|
|
|
|
|
|
|
if (!it)
|
|
|
|
return std::nullopt;
|
|
|
|
|
2023-01-04 12:53:17 -08:00
|
|
|
const FunctionType* ftv = get<FunctionType>(follow(*it));
|
2021-12-10 14:05:05 -08:00
|
|
|
|
|
|
|
if (!ftv)
|
|
|
|
return std::nullopt;
|
|
|
|
|
|
|
|
auto [head, tail] = flatten(ftv->argTypes);
|
|
|
|
unsigned index = exprCall->self ? 1 : 0;
|
|
|
|
|
|
|
|
if (index < head.size())
|
|
|
|
return head[index];
|
|
|
|
|
|
|
|
return std::nullopt;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
auto it = module.astExpectedTypes.find(expr);
|
|
|
|
if (!it)
|
|
|
|
return std::nullopt;
|
|
|
|
|
|
|
|
return *it;
|
|
|
|
}
|
|
|
|
|
2023-01-04 12:53:17 -08:00
|
|
|
static bool checkTypeMatch(TypeId subTy, TypeId superTy, NotNull<Scope> scope, TypeArena* typeArena, NotNull<BuiltinTypes> builtinTypes)
|
2022-03-17 17:46:04 -07:00
|
|
|
{
|
|
|
|
InternalErrorReporter iceReporter;
|
|
|
|
UnifierSharedState unifierState(&iceReporter);
|
2023-01-04 12:53:17 -08:00
|
|
|
Normalizer normalizer{typeArena, builtinTypes, NotNull{&unifierState}};
|
2022-03-17 17:46:04 -07:00
|
|
|
|
2023-11-03 16:45:04 -07:00
|
|
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
|
|
|
{
|
|
|
|
Subtyping subtyping{builtinTypes, NotNull{typeArena}, NotNull{&normalizer}, NotNull{&iceReporter}, scope};
|
|
|
|
|
|
|
|
return subtyping.isSubtype(subTy, superTy).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;
|
|
|
|
|
|
|
|
return unifier.canUnify(subTy, superTy).empty();
|
|
|
|
}
|
2023-03-03 22:21:14 +02:00
|
|
|
|
2022-03-17 17:46:04 -07:00
|
|
|
}
|
|
|
|
|
2022-09-08 15:14:25 -07:00
|
|
|
static TypeCorrectKind checkTypeCorrectKind(
|
2023-01-04 12:53:17 -08:00
|
|
|
const Module& module, TypeArena* typeArena, NotNull<BuiltinTypes> builtinTypes, AstNode* node, Position position, TypeId ty)
|
2021-10-29 13:25:12 -07:00
|
|
|
{
|
|
|
|
ty = follow(ty);
|
|
|
|
|
2023-01-06 13:14:35 -08:00
|
|
|
LUAU_ASSERT(module.hasModuleScope());
|
|
|
|
|
2022-08-18 14:32:08 -07:00
|
|
|
NotNull<Scope> moduleScope{module.getModuleScope().get()};
|
|
|
|
|
2022-01-27 15:46:05 -08:00
|
|
|
auto typeAtPosition = findExpectedTypeAt(module, node, position);
|
2021-10-29 13:25:12 -07:00
|
|
|
|
2022-01-27 15:46:05 -08:00
|
|
|
if (!typeAtPosition)
|
|
|
|
return TypeCorrectKind::None;
|
2021-10-29 13:25:12 -07:00
|
|
|
|
2022-01-27 15:46:05 -08:00
|
|
|
TypeId expectedType = follow(*typeAtPosition);
|
2021-12-10 14:05:05 -08:00
|
|
|
|
2023-01-04 12:53:17 -08:00
|
|
|
auto checkFunctionType = [typeArena, builtinTypes, moduleScope, &expectedType](const FunctionType* ftv) {
|
2022-10-06 17:23:29 -07:00
|
|
|
if (std::optional<TypeId> firstRetTy = first(ftv->retTypes))
|
2023-01-04 12:53:17 -08:00
|
|
|
return checkTypeMatch(*firstRetTy, expectedType, moduleScope, typeArena, builtinTypes);
|
2022-03-17 17:46:04 -07:00
|
|
|
|
2022-10-06 17:23:29 -07:00
|
|
|
return false;
|
2022-03-04 08:36:33 -08:00
|
|
|
};
|
|
|
|
|
|
|
|
// We also want to suggest functions that return compatible result
|
2023-01-04 12:53:17 -08:00
|
|
|
if (const FunctionType* ftv = get<FunctionType>(ty); ftv && checkFunctionType(ftv))
|
2022-03-04 08:36:33 -08:00
|
|
|
{
|
|
|
|
return TypeCorrectKind::CorrectFunctionResult;
|
2022-01-27 15:46:05 -08:00
|
|
|
}
|
2023-01-04 12:53:17 -08:00
|
|
|
else if (const IntersectionType* itv = get<IntersectionType>(ty))
|
2022-01-27 15:46:05 -08:00
|
|
|
{
|
2022-03-04 08:36:33 -08:00
|
|
|
for (TypeId id : itv->parts)
|
2022-01-27 15:46:05 -08:00
|
|
|
{
|
2023-01-04 12:53:17 -08:00
|
|
|
if (const FunctionType* ftv = get<FunctionType>(id); ftv && checkFunctionType(ftv))
|
2022-01-27 15:46:05 -08:00
|
|
|
{
|
2022-03-04 08:36:33 -08:00
|
|
|
return TypeCorrectKind::CorrectFunctionResult;
|
2022-01-27 15:46:05 -08:00
|
|
|
}
|
2021-12-02 22:41:04 -08:00
|
|
|
}
|
|
|
|
}
|
2022-01-14 08:20:09 -08:00
|
|
|
|
2023-01-06 13:14:35 -08:00
|
|
|
return checkTypeMatch(ty, expectedType, moduleScope, typeArena, builtinTypes) ? TypeCorrectKind::Correct : TypeCorrectKind::None;
|
2021-10-29 13:25:12 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
enum class PropIndexType
|
|
|
|
{
|
|
|
|
Point,
|
|
|
|
Colon,
|
|
|
|
Key,
|
|
|
|
};
|
|
|
|
|
2023-01-04 12:53:17 -08:00
|
|
|
static void autocompleteProps(const Module& module, TypeArena* typeArena, NotNull<BuiltinTypes> builtinTypes, TypeId rootTy, TypeId ty,
|
2022-09-08 15:14:25 -07:00
|
|
|
PropIndexType indexType, const std::vector<AstNode*>& nodes, AutocompleteEntryMap& result, std::unordered_set<TypeId>& seen,
|
2023-01-04 12:53:17 -08:00
|
|
|
std::optional<const ClassType*> containingClass = std::nullopt)
|
2021-10-29 13:25:12 -07:00
|
|
|
{
|
2022-10-06 17:23:29 -07:00
|
|
|
rootTy = follow(rootTy);
|
2021-10-29 13:25:12 -07:00
|
|
|
ty = follow(ty);
|
|
|
|
|
|
|
|
if (seen.count(ty))
|
|
|
|
return;
|
|
|
|
seen.insert(ty);
|
|
|
|
|
2023-01-04 12:53:17 -08:00
|
|
|
auto isWrongIndexer = [typeArena, builtinTypes, &module, rootTy, indexType](Luau::TypeId type) {
|
2022-03-17 17:46:04 -07:00
|
|
|
if (indexType == PropIndexType::Key)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
bool calledWithSelf = indexType == PropIndexType::Colon;
|
|
|
|
|
2023-01-04 12:53:17 -08:00
|
|
|
auto isCompatibleCall = [typeArena, builtinTypes, &module, rootTy, calledWithSelf](const FunctionType* ftv) {
|
2022-07-28 21:24:07 -07:00
|
|
|
// Strong match with definition is a success
|
|
|
|
if (calledWithSelf == ftv->hasSelf)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
// Calls on classes require strict match between how function is declared and how it's called
|
2023-01-04 12:53:17 -08:00
|
|
|
if (get<ClassType>(rootTy))
|
2022-07-28 21:24:07 -07:00
|
|
|
return false;
|
2022-03-17 17:46:04 -07:00
|
|
|
|
2022-07-28 21:24:07 -07:00
|
|
|
// 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
|
|
|
|
if (std::optional<TypeId> firstArgTy = first(ftv->argTypes))
|
2022-03-17 17:46:04 -07:00
|
|
|
{
|
2023-01-04 12:53:17 -08:00
|
|
|
if (checkTypeMatch(rootTy, *firstArgTy, NotNull{module.getModuleScope().get()}, typeArena, builtinTypes))
|
2022-07-28 21:24:07 -07:00
|
|
|
return calledWithSelf;
|
2022-03-17 17:46:04 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
return !calledWithSelf;
|
|
|
|
};
|
|
|
|
|
2023-01-04 12:53:17 -08:00
|
|
|
if (const FunctionType* ftv = get<FunctionType>(type))
|
2022-03-17 17:46:04 -07:00
|
|
|
return !isCompatibleCall(ftv);
|
|
|
|
|
|
|
|
// For intersections, any part that is successful makes the whole call successful
|
2023-01-04 12:53:17 -08:00
|
|
|
if (const IntersectionType* itv = get<IntersectionType>(type))
|
2022-03-17 17:46:04 -07:00
|
|
|
{
|
|
|
|
for (auto subType : itv->parts)
|
|
|
|
{
|
2023-01-04 12:53:17 -08:00
|
|
|
if (const FunctionType* ftv = get<FunctionType>(Luau::follow(subType)))
|
2022-03-17 17:46:04 -07:00
|
|
|
{
|
|
|
|
if (isCompatibleCall(ftv))
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return calledWithSelf;
|
|
|
|
};
|
2021-10-29 13:25:12 -07:00
|
|
|
|
2023-01-04 12:53:17 -08:00
|
|
|
auto fillProps = [&](const ClassType::Props& props) {
|
2021-10-29 13:25:12 -07:00
|
|
|
for (const auto& [name, prop] : props)
|
|
|
|
{
|
|
|
|
// We are walking up the class hierarchy, so if we encounter a property that we have
|
|
|
|
// already populated, it takes precedence over the property we found just now.
|
2022-02-17 17:18:01 -08:00
|
|
|
if (result.count(name) == 0 && name != kParseNameError)
|
2021-10-29 13:25:12 -07:00
|
|
|
{
|
2023-05-26 00:36:34 +03:00
|
|
|
Luau::TypeId type;
|
|
|
|
|
|
|
|
if (FFlag::DebugLuauReadWriteProperties)
|
|
|
|
{
|
|
|
|
if (auto ty = prop.readType())
|
|
|
|
type = follow(*ty);
|
|
|
|
else
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
type = follow(prop.type());
|
|
|
|
|
2022-09-08 15:14:25 -07:00
|
|
|
TypeCorrectKind typeCorrect = indexType == PropIndexType::Key
|
|
|
|
? TypeCorrectKind::Correct
|
2023-01-04 12:53:17 -08:00
|
|
|
: checkTypeCorrectKind(module, typeArena, builtinTypes, nodes.back(), {{}, {}}, type);
|
2023-05-26 00:36:34 +03:00
|
|
|
|
2021-10-29 13:25:12 -07:00
|
|
|
ParenthesesRecommendation parens =
|
|
|
|
indexType == PropIndexType::Key ? ParenthesesRecommendation::None : getParenRecommendation(type, nodes, typeCorrect);
|
|
|
|
|
2023-09-15 10:26:59 -07:00
|
|
|
result[name] = AutocompleteEntry{AutocompleteEntryKind::Property, type, prop.deprecated, isWrongIndexer(type), typeCorrect,
|
|
|
|
containingClass, &prop, prop.documentationSymbol, {}, parens, {}, indexType == PropIndexType::Colon};
|
2021-10-29 13:25:12 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2023-01-04 12:53:17 -08:00
|
|
|
auto fillMetatableProps = [&](const TableType* mtable) {
|
2022-03-17 17:46:04 -07:00
|
|
|
auto indexIt = mtable->props.find("__index");
|
|
|
|
if (indexIt != mtable->props.end())
|
|
|
|
{
|
2023-04-28 22:55:13 +03:00
|
|
|
TypeId followed = follow(indexIt->second.type());
|
2023-01-04 12:53:17 -08:00
|
|
|
if (get<TableType>(followed) || get<MetatableType>(followed))
|
2022-03-17 17:46:04 -07:00
|
|
|
{
|
2023-01-04 12:53:17 -08:00
|
|
|
autocompleteProps(module, typeArena, builtinTypes, rootTy, followed, indexType, nodes, result, seen);
|
2022-03-17 17:46:04 -07:00
|
|
|
}
|
2023-01-04 12:53:17 -08:00
|
|
|
else if (auto indexFunction = get<FunctionType>(followed))
|
2022-03-17 17:46:04 -07:00
|
|
|
{
|
2022-06-16 18:05:14 -07:00
|
|
|
std::optional<TypeId> indexFunctionResult = first(indexFunction->retTypes);
|
2022-03-17 17:46:04 -07:00
|
|
|
if (indexFunctionResult)
|
2023-01-04 12:53:17 -08:00
|
|
|
autocompleteProps(module, typeArena, builtinTypes, rootTy, *indexFunctionResult, indexType, nodes, result, seen);
|
2022-03-17 17:46:04 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2023-01-04 12:53:17 -08:00
|
|
|
if (auto cls = get<ClassType>(ty))
|
2021-10-29 13:25:12 -07:00
|
|
|
{
|
|
|
|
containingClass = containingClass.value_or(cls);
|
|
|
|
fillProps(cls->props);
|
|
|
|
if (cls->parent)
|
2023-01-04 12:53:17 -08:00
|
|
|
autocompleteProps(module, typeArena, builtinTypes, rootTy, *cls->parent, indexType, nodes, result, seen, containingClass);
|
2021-10-29 13:25:12 -07:00
|
|
|
}
|
2023-01-04 12:53:17 -08:00
|
|
|
else if (auto tbl = get<TableType>(ty))
|
2021-10-29 13:25:12 -07:00
|
|
|
fillProps(tbl->props);
|
2023-01-04 12:53:17 -08:00
|
|
|
else if (auto mt = get<MetatableType>(ty))
|
2021-10-29 13:25:12 -07:00
|
|
|
{
|
2023-01-04 12:53:17 -08:00
|
|
|
autocompleteProps(module, typeArena, builtinTypes, rootTy, mt->table, indexType, nodes, result, seen);
|
2021-10-29 13:25:12 -07:00
|
|
|
|
2023-03-03 22:21:14 +02:00
|
|
|
if (auto mtable = get<TableType>(follow(mt->metatable)))
|
2022-10-06 17:23:29 -07:00
|
|
|
fillMetatableProps(mtable);
|
2021-10-29 13:25:12 -07:00
|
|
|
}
|
2023-01-04 12:53:17 -08:00
|
|
|
else if (auto i = get<IntersectionType>(ty))
|
2021-10-29 13:25:12 -07:00
|
|
|
{
|
|
|
|
// Complete all properties in every variant
|
|
|
|
for (TypeId ty : i->parts)
|
|
|
|
{
|
|
|
|
AutocompleteEntryMap inner;
|
|
|
|
std::unordered_set<TypeId> innerSeen = seen;
|
|
|
|
|
2023-01-04 12:53:17 -08:00
|
|
|
autocompleteProps(module, typeArena, builtinTypes, rootTy, ty, indexType, nodes, inner, innerSeen);
|
2021-10-29 13:25:12 -07:00
|
|
|
|
|
|
|
for (auto& pair : inner)
|
|
|
|
result.insert(pair);
|
|
|
|
}
|
|
|
|
}
|
2023-01-04 12:53:17 -08:00
|
|
|
else if (auto u = get<UnionType>(ty))
|
2021-10-29 13:25:12 -07:00
|
|
|
{
|
|
|
|
// Complete all properties common to all variants
|
|
|
|
auto iter = begin(u);
|
|
|
|
auto endIter = end(u);
|
|
|
|
|
|
|
|
while (iter != endIter)
|
|
|
|
{
|
2021-11-12 06:27:34 -08:00
|
|
|
if (isNil(*iter))
|
|
|
|
++iter;
|
2021-10-29 13:25:12 -07:00
|
|
|
else
|
2021-11-12 06:27:34 -08:00
|
|
|
break;
|
2021-10-29 13:25:12 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if (iter == endIter)
|
|
|
|
return;
|
|
|
|
|
2023-01-04 12:53:17 -08:00
|
|
|
autocompleteProps(module, typeArena, builtinTypes, rootTy, *iter, indexType, nodes, result, seen);
|
2021-10-29 13:25:12 -07:00
|
|
|
|
|
|
|
++iter;
|
|
|
|
|
|
|
|
while (iter != endIter)
|
|
|
|
{
|
|
|
|
AutocompleteEntryMap inner;
|
2022-03-17 17:46:04 -07:00
|
|
|
std::unordered_set<TypeId> innerSeen;
|
|
|
|
|
2021-11-12 06:27:34 -08:00
|
|
|
if (isNil(*iter))
|
2021-10-29 13:25:12 -07:00
|
|
|
{
|
2021-11-12 06:27:34 -08:00
|
|
|
++iter;
|
|
|
|
continue;
|
2021-10-29 13:25:12 -07:00
|
|
|
}
|
|
|
|
|
2023-01-04 12:53:17 -08:00
|
|
|
autocompleteProps(module, typeArena, builtinTypes, rootTy, *iter, indexType, nodes, inner, innerSeen);
|
2021-10-29 13:25:12 -07:00
|
|
|
|
|
|
|
std::unordered_set<std::string> toRemove;
|
|
|
|
|
|
|
|
for (const auto& [k, v] : result)
|
|
|
|
{
|
|
|
|
(void)v;
|
|
|
|
if (!inner.count(k))
|
|
|
|
toRemove.insert(k);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const std::string& k : toRemove)
|
|
|
|
result.erase(k);
|
|
|
|
|
|
|
|
++iter;
|
|
|
|
}
|
|
|
|
}
|
2023-01-04 12:53:17 -08:00
|
|
|
else if (auto pt = get<PrimitiveType>(ty))
|
2022-03-17 17:46:04 -07:00
|
|
|
{
|
|
|
|
if (pt->metatable)
|
|
|
|
{
|
2023-01-04 12:53:17 -08:00
|
|
|
if (auto mtable = get<TableType>(*pt->metatable))
|
2022-03-17 17:46:04 -07:00
|
|
|
fillMetatableProps(mtable);
|
|
|
|
}
|
|
|
|
}
|
2023-01-04 12:53:17 -08:00
|
|
|
else if (get<StringSingleton>(get<SingletonType>(ty)))
|
2022-03-17 17:46:04 -07:00
|
|
|
{
|
2023-01-04 12:53:17 -08:00
|
|
|
autocompleteProps(module, typeArena, builtinTypes, rootTy, builtinTypes->stringType, indexType, nodes, result, seen);
|
2022-03-17 17:46:04 -07:00
|
|
|
}
|
2021-10-29 13:25:12 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
static void autocompleteKeywords(
|
|
|
|
const SourceModule& sourceModule, const std::vector<AstNode*>& ancestry, Position position, AutocompleteEntryMap& result)
|
|
|
|
{
|
|
|
|
LUAU_ASSERT(!ancestry.empty());
|
|
|
|
|
|
|
|
AstNode* node = ancestry.back();
|
|
|
|
|
|
|
|
if (!node->is<AstExprFunction>() && node->asExpr())
|
|
|
|
{
|
|
|
|
// This is not strictly correct. We should recommend `and` and `or` only after
|
|
|
|
// another expression, not at the start of a new one. We should only recommend
|
|
|
|
// `not` at the start of an expression. Detecting either case reliably is quite
|
|
|
|
// complex, however; this is good enough for now.
|
|
|
|
|
|
|
|
// These are not context-sensitive keywords, so we can unconditionally assign.
|
|
|
|
result["and"] = {AutocompleteEntryKind::Keyword};
|
|
|
|
result["or"] = {AutocompleteEntryKind::Keyword};
|
|
|
|
result["not"] = {AutocompleteEntryKind::Keyword};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-04 12:53:17 -08:00
|
|
|
static void autocompleteProps(const Module& module, TypeArena* typeArena, NotNull<BuiltinTypes> builtinTypes, TypeId ty, PropIndexType indexType,
|
2022-09-08 15:14:25 -07:00
|
|
|
const std::vector<AstNode*>& nodes, AutocompleteEntryMap& result)
|
2021-10-29 13:25:12 -07:00
|
|
|
{
|
|
|
|
std::unordered_set<TypeId> seen;
|
2023-01-04 12:53:17 -08:00
|
|
|
autocompleteProps(module, typeArena, builtinTypes, ty, ty, indexType, nodes, result, seen);
|
2021-10-29 13:25:12 -07:00
|
|
|
}
|
|
|
|
|
2023-01-04 12:53:17 -08:00
|
|
|
AutocompleteEntryMap autocompleteProps(const Module& module, TypeArena* typeArena, NotNull<BuiltinTypes> builtinTypes, TypeId ty,
|
2022-09-08 15:14:25 -07:00
|
|
|
PropIndexType indexType, const std::vector<AstNode*>& nodes)
|
2021-10-29 13:25:12 -07:00
|
|
|
{
|
|
|
|
AutocompleteEntryMap result;
|
2023-01-04 12:53:17 -08:00
|
|
|
autocompleteProps(module, typeArena, builtinTypes, ty, indexType, nodes, result);
|
2021-10-29 13:25:12 -07:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
AutocompleteEntryMap autocompleteModuleTypes(const Module& module, Position position, std::string_view moduleName)
|
|
|
|
{
|
|
|
|
AutocompleteEntryMap result;
|
|
|
|
|
|
|
|
for (ScopePtr scope = findScopeAtPosition(module, position); scope; scope = scope->parent)
|
|
|
|
{
|
|
|
|
if (auto it = scope->importedTypeBindings.find(std::string(moduleName)); it != scope->importedTypeBindings.end())
|
|
|
|
{
|
|
|
|
for (const auto& [name, ty] : it->second)
|
|
|
|
result[name] = AutocompleteEntry{AutocompleteEntryKind::Type, ty.type};
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2023-08-25 10:23:55 -07:00
|
|
|
static void autocompleteStringSingleton(TypeId ty, bool addQuotes, AstNode* node, Position position, AutocompleteEntryMap& result)
|
2022-04-07 14:29:01 -07:00
|
|
|
{
|
2023-08-25 10:23:55 -07:00
|
|
|
if (FFlag::LuauAutocompleteStringLiteralBounds)
|
|
|
|
{
|
|
|
|
if (position == node->location.begin || position == node->location.end)
|
|
|
|
{
|
|
|
|
if (auto str = node->as<AstExprConstantString>(); str && str->quoteStyle == AstExprConstantString::Quoted)
|
|
|
|
return;
|
|
|
|
else if (node->is<AstExprInterpString>())
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-07 14:29:01 -07:00
|
|
|
auto formatKey = [addQuotes](const std::string& key) {
|
|
|
|
if (addQuotes)
|
|
|
|
return "\"" + escape(key) + "\"";
|
|
|
|
|
|
|
|
return escape(key);
|
|
|
|
};
|
|
|
|
|
|
|
|
ty = follow(ty);
|
|
|
|
|
2023-01-04 12:53:17 -08:00
|
|
|
if (auto ss = get<StringSingleton>(get<SingletonType>(ty)))
|
2022-04-07 14:29:01 -07:00
|
|
|
{
|
|
|
|
result[formatKey(ss->value)] = AutocompleteEntry{AutocompleteEntryKind::String, ty, false, false, TypeCorrectKind::Correct};
|
|
|
|
}
|
2023-01-04 12:53:17 -08:00
|
|
|
else if (auto uty = get<UnionType>(ty))
|
2022-04-07 14:29:01 -07:00
|
|
|
{
|
|
|
|
for (auto el : uty)
|
|
|
|
{
|
2023-01-04 12:53:17 -08:00
|
|
|
if (auto ss = get<StringSingleton>(get<SingletonType>(el)))
|
2022-04-07 14:29:01 -07:00
|
|
|
result[formatKey(ss->value)] = AutocompleteEntry{AutocompleteEntryKind::String, ty, false, false, TypeCorrectKind::Correct};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2021-10-29 13:25:12 -07:00
|
|
|
static bool canSuggestInferredType(ScopePtr scope, TypeId ty)
|
|
|
|
{
|
|
|
|
ty = follow(ty);
|
|
|
|
|
|
|
|
// No point in suggesting 'any', invalid to suggest others
|
2023-01-04 12:53:17 -08:00
|
|
|
if (get<AnyType>(ty) || get<ErrorType>(ty) || get<GenericType>(ty) || get<FreeType>(ty))
|
2021-10-29 13:25:12 -07:00
|
|
|
return false;
|
|
|
|
|
|
|
|
// No syntax for unnamed tables with a metatable
|
2023-01-04 12:53:17 -08:00
|
|
|
if (get<MetatableType>(ty))
|
2021-10-29 13:25:12 -07:00
|
|
|
return false;
|
|
|
|
|
2023-01-04 12:53:17 -08:00
|
|
|
if (const TableType* ttv = get<TableType>(ty))
|
2021-10-29 13:25:12 -07:00
|
|
|
{
|
|
|
|
if (ttv->name)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
if (ttv->syntheticName)
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// We might still have a type with cycles or one that is too long, we'll check that later
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Walk complex type trees to find the element that is being edited
|
|
|
|
static std::optional<TypeId> findTypeElementAt(AstType* astType, TypeId ty, Position position);
|
|
|
|
|
|
|
|
static std::optional<TypeId> findTypeElementAt(const AstTypeList& astTypeList, TypePackId tp, Position position)
|
|
|
|
{
|
|
|
|
for (size_t i = 0; i < astTypeList.types.size; i++)
|
|
|
|
{
|
|
|
|
AstType* type = astTypeList.types.data[i];
|
|
|
|
|
|
|
|
if (type->location.containsClosed(position))
|
|
|
|
{
|
|
|
|
auto [head, _] = flatten(tp);
|
|
|
|
|
|
|
|
if (i < head.size())
|
|
|
|
return findTypeElementAt(type, head[i], position);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (AstTypePack* argTp = astTypeList.tailType)
|
|
|
|
{
|
|
|
|
if (auto variadic = argTp->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)
|
|
|
|
{
|
|
|
|
ty = follow(ty);
|
|
|
|
|
|
|
|
if (astType->is<AstTypeReference>())
|
|
|
|
return ty;
|
|
|
|
|
|
|
|
if (astType->is<AstTypeError>())
|
|
|
|
return ty;
|
|
|
|
|
|
|
|
if (AstTypeFunction* type = astType->as<AstTypeFunction>())
|
|
|
|
{
|
2023-01-04 12:53:17 -08:00
|
|
|
const FunctionType* ftv = get<FunctionType>(ty);
|
2021-10-29 13:25:12 -07:00
|
|
|
|
|
|
|
if (!ftv)
|
|
|
|
return {};
|
|
|
|
|
|
|
|
if (auto element = findTypeElementAt(type->argTypes, ftv->argTypes, position))
|
|
|
|
return element;
|
|
|
|
|
2022-06-16 18:05:14 -07:00
|
|
|
if (auto element = findTypeElementAt(type->returnTypes, ftv->retTypes, position))
|
2021-10-29 13:25:12 -07:00
|
|
|
return element;
|
|
|
|
}
|
|
|
|
|
|
|
|
// It's possible to walk through other types like intrsection and unions if we find value in doing that
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
std::optional<TypeId> getLocalTypeInScopeAt(const Module& module, Position position, AstLocal* local)
|
|
|
|
{
|
|
|
|
if (ScopePtr scope = findScopeAtPosition(module, position))
|
|
|
|
{
|
|
|
|
for (const auto& [name, binding] : scope->bindings)
|
|
|
|
{
|
|
|
|
if (name == local)
|
|
|
|
return binding.typeId;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
2023-09-15 10:26:59 -07:00
|
|
|
template<typename T>
|
2023-07-28 08:13:53 -07:00
|
|
|
static std::optional<std::string> tryToStringDetailed(const ScopePtr& scope, T ty, bool functionTypeArguments)
|
2021-10-29 13:25:12 -07:00
|
|
|
{
|
|
|
|
ToStringOptions opts;
|
|
|
|
opts.useLineBreaks = false;
|
|
|
|
opts.hideTableKind = true;
|
2023-07-28 08:13:53 -07:00
|
|
|
opts.functionTypeArguments = functionTypeArguments;
|
2021-10-29 13:25:12 -07:00
|
|
|
opts.scope = scope;
|
|
|
|
ToStringResult name = toStringDetailed(ty, opts);
|
|
|
|
|
|
|
|
if (name.error || name.invalid || name.cycle || name.truncated)
|
|
|
|
return std::nullopt;
|
|
|
|
|
|
|
|
return name.name;
|
|
|
|
}
|
|
|
|
|
2023-07-28 08:13:53 -07:00
|
|
|
static std::optional<Name> tryGetTypeNameInScope(ScopePtr scope, TypeId ty, bool functionTypeArguments = false)
|
|
|
|
{
|
|
|
|
if (!canSuggestInferredType(scope, ty))
|
|
|
|
return std::nullopt;
|
|
|
|
|
2023-09-07 17:13:49 -07:00
|
|
|
return tryToStringDetailed(scope, ty, functionTypeArguments);
|
2023-07-28 08:13:53 -07:00
|
|
|
}
|
|
|
|
|
2021-10-29 13:25:12 -07:00
|
|
|
static bool tryAddTypeCorrectSuggestion(AutocompleteEntryMap& result, ScopePtr scope, AstType* topType, TypeId inferredType, Position position)
|
|
|
|
{
|
|
|
|
std::optional<TypeId> ty;
|
|
|
|
|
|
|
|
if (topType)
|
|
|
|
ty = findTypeElementAt(topType, inferredType, position);
|
|
|
|
else
|
|
|
|
ty = inferredType;
|
|
|
|
|
|
|
|
if (!ty)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (auto name = tryGetTypeNameInScope(scope, *ty))
|
|
|
|
{
|
|
|
|
if (auto it = result.find(*name); it != result.end())
|
|
|
|
it->second.typeCorrect = TypeCorrectKind::Correct;
|
|
|
|
else
|
|
|
|
result[*name] = AutocompleteEntry{AutocompleteEntryKind::Type, *ty, false, false, TypeCorrectKind::Correct};
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
static std::optional<TypeId> tryGetTypePackTypeAt(TypePackId tp, size_t index)
|
|
|
|
{
|
|
|
|
auto [tpHead, tpTail] = flatten(tp);
|
|
|
|
|
|
|
|
if (index < tpHead.size())
|
|
|
|
return tpHead[index];
|
|
|
|
|
|
|
|
// Infinite tail
|
|
|
|
if (tpTail)
|
|
|
|
{
|
|
|
|
if (const VariadicTypePack* vtp = get<VariadicTypePack>(follow(*tpTail)))
|
|
|
|
return vtp->ty;
|
|
|
|
}
|
|
|
|
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
template<typename T>
|
2023-01-04 12:53:17 -08:00
|
|
|
std::optional<const T*> returnFirstNonnullOptionOfType(const UnionType* utv)
|
2021-10-29 13:25:12 -07:00
|
|
|
{
|
|
|
|
std::optional<const T*> ret;
|
|
|
|
for (TypeId subTy : utv)
|
|
|
|
{
|
|
|
|
if (isNil(subTy))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (const T* ftv = get<T>(follow(subTy)))
|
|
|
|
{
|
|
|
|
if (ret.has_value())
|
|
|
|
{
|
|
|
|
return std::nullopt;
|
|
|
|
}
|
|
|
|
ret = ftv;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return std::nullopt;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2021-12-10 14:05:05 -08:00
|
|
|
static std::optional<bool> functionIsExpectedAt(const Module& module, AstNode* node, Position position)
|
2021-10-29 13:25:12 -07:00
|
|
|
{
|
2022-01-27 15:46:05 -08:00
|
|
|
auto typeAtPosition = findExpectedTypeAt(module, node, position);
|
2021-10-29 13:25:12 -07:00
|
|
|
|
2022-01-27 15:46:05 -08:00
|
|
|
if (!typeAtPosition)
|
|
|
|
return std::nullopt;
|
2021-12-10 14:05:05 -08:00
|
|
|
|
2022-01-27 15:46:05 -08:00
|
|
|
TypeId expectedType = follow(*typeAtPosition);
|
2021-10-29 13:25:12 -07:00
|
|
|
|
2023-01-04 12:53:17 -08:00
|
|
|
if (get<FunctionType>(expectedType))
|
2021-10-29 13:25:12 -07:00
|
|
|
return true;
|
|
|
|
|
2023-01-04 12:53:17 -08:00
|
|
|
if (const IntersectionType* itv = get<IntersectionType>(expectedType))
|
2021-10-29 13:25:12 -07:00
|
|
|
{
|
|
|
|
return std::all_of(begin(itv->parts), end(itv->parts), [](auto&& ty) {
|
2023-01-04 12:53:17 -08:00
|
|
|
return get<FunctionType>(Luau::follow(ty)) != nullptr;
|
2021-10-29 13:25:12 -07:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2023-01-04 12:53:17 -08:00
|
|
|
if (const UnionType* utv = get<UnionType>(expectedType))
|
|
|
|
return returnFirstNonnullOptionOfType<FunctionType>(utv).has_value();
|
2021-10-29 13:25:12 -07:00
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
AutocompleteEntryMap autocompleteTypeNames(const Module& module, Position position, const std::vector<AstNode*>& ancestry)
|
|
|
|
{
|
|
|
|
AutocompleteEntryMap result;
|
|
|
|
|
|
|
|
ScopePtr startScope = findScopeAtPosition(module, position);
|
|
|
|
|
|
|
|
for (ScopePtr scope = startScope; scope; scope = scope->parent)
|
|
|
|
{
|
|
|
|
for (const auto& [name, ty] : scope->exportedTypeBindings)
|
|
|
|
{
|
|
|
|
if (!result.count(name))
|
|
|
|
result[name] = AutocompleteEntry{AutocompleteEntryKind::Type, ty.type, false, false, TypeCorrectKind::None, std::nullopt,
|
|
|
|
std::nullopt, ty.type->documentationSymbol};
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const auto& [name, ty] : scope->privateTypeBindings)
|
|
|
|
{
|
|
|
|
if (!result.count(name))
|
|
|
|
result[name] = AutocompleteEntry{AutocompleteEntryKind::Type, ty.type, false, false, TypeCorrectKind::None, std::nullopt,
|
|
|
|
std::nullopt, ty.type->documentationSymbol};
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const auto& [name, _] : scope->importedTypeBindings)
|
|
|
|
{
|
|
|
|
if (auto binding = scope->linearSearchForBinding(name, true))
|
|
|
|
{
|
|
|
|
if (!result.count(name))
|
|
|
|
result[name] = AutocompleteEntry{AutocompleteEntryKind::Module, binding->typeId};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
AstNode* parent = nullptr;
|
2022-07-07 18:22:39 -07:00
|
|
|
AstType* topType = nullptr; // TODO: rename?
|
2021-10-29 13:25:12 -07:00
|
|
|
|
|
|
|
for (auto it = ancestry.rbegin(), e = ancestry.rend(); it != e; ++it)
|
|
|
|
{
|
|
|
|
if (AstType* asType = (*it)->asType())
|
|
|
|
{
|
|
|
|
topType = asType;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
parent = *it;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!parent)
|
|
|
|
return result;
|
|
|
|
|
|
|
|
if (AstStatLocal* node = parent->as<AstStatLocal>()) // Try to provide inferred type of the local
|
|
|
|
{
|
|
|
|
// Look at which of the variable types we are defining
|
|
|
|
for (size_t i = 0; i < node->vars.size; i++)
|
|
|
|
{
|
|
|
|
AstLocal* var = node->vars.data[i];
|
|
|
|
|
|
|
|
if (var->annotation && var->annotation->location.containsClosed(position))
|
|
|
|
{
|
|
|
|
if (node->values.size == 0)
|
|
|
|
break;
|
|
|
|
|
|
|
|
unsigned tailPos = 0;
|
|
|
|
|
|
|
|
// For multiple return values we will try to unpack last function call return type pack
|
|
|
|
if (i >= node->values.size)
|
|
|
|
{
|
|
|
|
tailPos = int(i) - int(node->values.size) + 1;
|
|
|
|
i = int(node->values.size) - 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
AstExpr* expr = node->values.data[i]->asExpr();
|
|
|
|
|
|
|
|
if (!expr)
|
|
|
|
break;
|
|
|
|
|
|
|
|
TypeId inferredType = nullptr;
|
|
|
|
|
|
|
|
if (AstExprCall* exprCall = expr->as<AstExprCall>())
|
|
|
|
{
|
2021-11-04 19:34:35 -07:00
|
|
|
if (auto it = module.astTypes.find(exprCall->func))
|
2021-10-29 13:25:12 -07:00
|
|
|
{
|
2023-01-04 12:53:17 -08:00
|
|
|
if (const FunctionType* ftv = get<FunctionType>(follow(*it)))
|
2021-10-29 13:25:12 -07:00
|
|
|
{
|
2022-06-16 18:05:14 -07:00
|
|
|
if (auto ty = tryGetTypePackTypeAt(ftv->retTypes, tailPos))
|
2021-10-29 13:25:12 -07:00
|
|
|
inferredType = *ty;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (tailPos != 0)
|
|
|
|
break;
|
|
|
|
|
2021-11-04 19:34:35 -07:00
|
|
|
if (auto it = module.astTypes.find(expr))
|
|
|
|
inferredType = *it;
|
2021-10-29 13:25:12 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if (inferredType)
|
|
|
|
tryAddTypeCorrectSuggestion(result, startScope, topType, inferredType, position);
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (AstExprFunction* node = parent->as<AstExprFunction>())
|
|
|
|
{
|
|
|
|
// For lookup inside expected function type if that's available
|
2023-01-04 12:53:17 -08:00
|
|
|
auto tryGetExpectedFunctionType = [](const Module& module, AstExpr* expr) -> const FunctionType* {
|
2021-10-29 13:25:12 -07:00
|
|
|
auto it = module.astExpectedTypes.find(expr);
|
|
|
|
|
2021-11-04 19:34:35 -07:00
|
|
|
if (!it)
|
2021-10-29 13:25:12 -07:00
|
|
|
return nullptr;
|
|
|
|
|
2021-11-04 19:34:35 -07:00
|
|
|
TypeId ty = follow(*it);
|
2021-10-29 13:25:12 -07:00
|
|
|
|
2023-01-04 12:53:17 -08:00
|
|
|
if (const FunctionType* ftv = get<FunctionType>(ty))
|
2021-10-29 13:25:12 -07:00
|
|
|
return ftv;
|
|
|
|
|
|
|
|
// Handle optional function type
|
2023-01-04 12:53:17 -08:00
|
|
|
if (const UnionType* utv = get<UnionType>(ty))
|
2021-10-29 13:25:12 -07:00
|
|
|
{
|
2023-01-04 12:53:17 -08:00
|
|
|
return returnFirstNonnullOptionOfType<FunctionType>(utv).value_or(nullptr);
|
2021-10-29 13:25:12 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
return nullptr;
|
|
|
|
};
|
|
|
|
|
|
|
|
// Find which argument type we are defining
|
|
|
|
for (size_t i = 0; i < node->args.size; i++)
|
|
|
|
{
|
|
|
|
AstLocal* arg = node->args.data[i];
|
|
|
|
|
|
|
|
if (arg->annotation && arg->annotation->location.containsClosed(position))
|
|
|
|
{
|
2023-01-04 12:53:17 -08:00
|
|
|
if (const FunctionType* ftv = tryGetExpectedFunctionType(module, node))
|
2021-10-29 13:25:12 -07:00
|
|
|
{
|
|
|
|
if (auto ty = tryGetTypePackTypeAt(ftv->argTypes, i))
|
|
|
|
tryAddTypeCorrectSuggestion(result, startScope, topType, *ty, position);
|
|
|
|
}
|
|
|
|
// Otherwise, try to use the type inferred by typechecker
|
|
|
|
else if (auto inferredType = getLocalTypeInScopeAt(module, position, arg))
|
|
|
|
{
|
|
|
|
tryAddTypeCorrectSuggestion(result, startScope, topType, *inferredType, position);
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (AstTypePack* argTp = node->varargAnnotation)
|
|
|
|
{
|
|
|
|
if (auto variadic = argTp->as<AstTypePackVariadic>())
|
|
|
|
{
|
|
|
|
if (variadic->location.containsClosed(position))
|
|
|
|
{
|
2023-01-04 12:53:17 -08:00
|
|
|
if (const FunctionType* ftv = tryGetExpectedFunctionType(module, node))
|
2021-10-29 13:25:12 -07:00
|
|
|
{
|
|
|
|
if (auto ty = tryGetTypePackTypeAt(ftv->argTypes, ~0u))
|
|
|
|
tryAddTypeCorrectSuggestion(result, startScope, topType, *ty, position);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-17 17:18:01 -08:00
|
|
|
if (!node->returnAnnotation)
|
|
|
|
return result;
|
|
|
|
|
|
|
|
for (size_t i = 0; i < node->returnAnnotation->types.size; i++)
|
2021-10-29 13:25:12 -07:00
|
|
|
{
|
2022-02-17 17:18:01 -08:00
|
|
|
AstType* ret = node->returnAnnotation->types.data[i];
|
2021-10-29 13:25:12 -07:00
|
|
|
|
|
|
|
if (ret->location.containsClosed(position))
|
|
|
|
{
|
2023-01-04 12:53:17 -08:00
|
|
|
if (const FunctionType* ftv = tryGetExpectedFunctionType(module, node))
|
2021-10-29 13:25:12 -07:00
|
|
|
{
|
2022-06-16 18:05:14 -07:00
|
|
|
if (auto ty = tryGetTypePackTypeAt(ftv->retTypes, i))
|
2021-10-29 13:25:12 -07:00
|
|
|
tryAddTypeCorrectSuggestion(result, startScope, topType, *ty, position);
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: with additional type information, we could suggest inferred return type here
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-17 17:18:01 -08:00
|
|
|
if (AstTypePack* retTp = node->returnAnnotation->tailType)
|
2021-10-29 13:25:12 -07:00
|
|
|
{
|
|
|
|
if (auto variadic = retTp->as<AstTypePackVariadic>())
|
|
|
|
{
|
|
|
|
if (variadic->location.containsClosed(position))
|
|
|
|
{
|
2023-01-04 12:53:17 -08:00
|
|
|
if (const FunctionType* ftv = tryGetExpectedFunctionType(module, node))
|
2021-10-29 13:25:12 -07:00
|
|
|
{
|
2022-06-16 18:05:14 -07:00
|
|
|
if (auto ty = tryGetTypePackTypeAt(ftv->retTypes, ~0u))
|
2021-10-29 13:25:12 -07:00
|
|
|
tryAddTypeCorrectSuggestion(result, startScope, topType, *ty, position);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool isInLocalNames(const std::vector<AstNode*>& ancestry, Position position)
|
|
|
|
{
|
|
|
|
for (auto iter = ancestry.rbegin(); iter != ancestry.rend(); iter++)
|
|
|
|
{
|
|
|
|
if (auto statLocal = (*iter)->as<AstStatLocal>())
|
|
|
|
{
|
|
|
|
for (auto var : statLocal->vars)
|
|
|
|
{
|
|
|
|
if (var->location.containsClosed(position))
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (auto funcExpr = (*iter)->as<AstExprFunction>())
|
|
|
|
{
|
|
|
|
if (funcExpr->argLocation && funcExpr->argLocation->contains(position))
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (auto localFunc = (*iter)->as<AstStatLocalFunction>())
|
|
|
|
{
|
|
|
|
return localFunc->name->location.containsClosed(position);
|
|
|
|
}
|
|
|
|
else if (auto block = (*iter)->as<AstStatBlock>())
|
|
|
|
{
|
|
|
|
if (block->body.size > 0)
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if ((*iter)->asStat())
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool isIdentifier(AstNode* node)
|
|
|
|
{
|
|
|
|
return node->is<AstExprGlobal>() || node->is<AstExprLocal>();
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool isBeingDefined(const std::vector<AstNode*>& ancestry, const Symbol& symbol)
|
|
|
|
{
|
|
|
|
// Current set of rules only check for local binding match
|
|
|
|
if (!symbol.local)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
for (auto iter = ancestry.rbegin(); iter != ancestry.rend(); iter++)
|
|
|
|
{
|
|
|
|
if (auto statLocal = (*iter)->as<AstStatLocal>())
|
|
|
|
{
|
|
|
|
for (auto var : statLocal->vars)
|
|
|
|
{
|
|
|
|
if (symbol.local == var)
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
template<typename T>
|
|
|
|
T* extractStat(const std::vector<AstNode*>& ancestry)
|
|
|
|
{
|
|
|
|
AstNode* node = ancestry.size() >= 1 ? ancestry.rbegin()[0] : nullptr;
|
|
|
|
if (!node)
|
|
|
|
return nullptr;
|
|
|
|
|
|
|
|
if (T* t = node->as<T>())
|
|
|
|
return t;
|
|
|
|
|
|
|
|
AstNode* parent = ancestry.size() >= 2 ? ancestry.rbegin()[1] : nullptr;
|
|
|
|
if (!parent)
|
|
|
|
return nullptr;
|
|
|
|
|
|
|
|
AstNode* grandParent = ancestry.size() >= 3 ? ancestry.rbegin()[2] : nullptr;
|
|
|
|
AstNode* greatGrandParent = ancestry.size() >= 4 ? ancestry.rbegin()[3] : nullptr;
|
2022-12-09 11:57:01 -08:00
|
|
|
|
2023-03-31 21:42:49 +03:00
|
|
|
if (!grandParent)
|
|
|
|
return nullptr;
|
2022-12-09 11:57:01 -08:00
|
|
|
|
2023-03-31 21:42:49 +03:00
|
|
|
if (T* t = parent->as<T>(); t && grandParent->is<AstStatBlock>())
|
|
|
|
return t;
|
2022-12-09 11:57:01 -08:00
|
|
|
|
2023-03-31 21:42:49 +03:00
|
|
|
if (!greatGrandParent)
|
|
|
|
return nullptr;
|
2021-10-29 13:25:12 -07:00
|
|
|
|
|
|
|
if (T* t = greatGrandParent->as<T>(); t && grandParent->is<AstStatBlock>() && parent->is<AstStatError>() && isIdentifier(node))
|
|
|
|
return t;
|
|
|
|
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2022-08-18 14:32:08 -07:00
|
|
|
static bool isBindingLegalAtCurrentPosition(const Symbol& symbol, const Binding& binding, Position pos)
|
2021-10-29 13:25:12 -07:00
|
|
|
{
|
2022-09-08 15:14:25 -07:00
|
|
|
if (symbol.local)
|
|
|
|
return binding.location.end < pos;
|
2022-08-18 14:32:08 -07:00
|
|
|
|
2022-09-08 15:14:25 -07:00
|
|
|
// Builtin globals have an empty location; for defined globals, we want pos to be outside of the definition range to suggest it
|
|
|
|
return binding.location == Location() || !binding.location.containsClosed(pos);
|
2021-10-29 13:25:12 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
static AutocompleteEntryMap autocompleteStatement(
|
|
|
|
const SourceModule& sourceModule, const Module& module, const std::vector<AstNode*>& ancestry, Position position)
|
|
|
|
{
|
|
|
|
// This is inefficient. :(
|
|
|
|
ScopePtr scope = findScopeAtPosition(module, position);
|
|
|
|
|
|
|
|
AutocompleteEntryMap result;
|
|
|
|
|
|
|
|
if (isInLocalNames(ancestry, position))
|
|
|
|
{
|
|
|
|
autocompleteKeywords(sourceModule, ancestry, position, result);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
while (scope)
|
|
|
|
{
|
|
|
|
for (const auto& [name, binding] : scope->bindings)
|
|
|
|
{
|
2022-08-18 14:32:08 -07:00
|
|
|
if (!isBindingLegalAtCurrentPosition(name, binding, position))
|
2021-10-29 13:25:12 -07:00
|
|
|
continue;
|
|
|
|
|
|
|
|
std::string n = toString(name);
|
|
|
|
if (!result.count(n))
|
|
|
|
result[n] = {AutocompleteEntryKind::Binding, binding.typeId, binding.deprecated, false, TypeCorrectKind::None, std::nullopt,
|
|
|
|
std::nullopt, binding.documentationSymbol, {}, getParenRecommendation(binding.typeId, ancestry, TypeCorrectKind::None)};
|
|
|
|
}
|
|
|
|
|
|
|
|
scope = scope->parent;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const auto& kw : kStatementStartingKeywords)
|
|
|
|
result.emplace(kw, AutocompleteEntry{AutocompleteEntryKind::Keyword});
|
|
|
|
|
2024-01-26 19:20:56 -08:00
|
|
|
for (auto it = ancestry.rbegin(); it != ancestry.rend(); ++it)
|
2021-10-29 13:25:12 -07:00
|
|
|
{
|
2024-01-26 19:20:56 -08:00
|
|
|
if (AstStatForIn* statForIn = (*it)->as<AstStatForIn>(); statForIn && !statForIn->body->hasEnd)
|
|
|
|
result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword});
|
|
|
|
else if (AstStatFor* statFor = (*it)->as<AstStatFor>(); statFor && !statFor->body->hasEnd)
|
|
|
|
result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword});
|
|
|
|
else if (AstStatIf* statIf = (*it)->as<AstStatIf>())
|
2023-09-01 10:58:27 -07:00
|
|
|
{
|
2024-01-26 19:20:56 -08:00
|
|
|
bool hasEnd = statIf->thenbody->hasEnd;
|
|
|
|
if (statIf->elsebody)
|
2023-10-13 13:20:12 -07:00
|
|
|
{
|
2024-01-26 19:20:56 -08:00
|
|
|
if (AstStatBlock* elseBlock = statIf->elsebody->as<AstStatBlock>())
|
|
|
|
hasEnd = elseBlock->hasEnd;
|
2023-10-13 13:20:12 -07:00
|
|
|
}
|
2024-01-26 19:20:56 -08:00
|
|
|
|
|
|
|
if (!hasEnd)
|
2024-01-12 14:25:27 -08:00
|
|
|
result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword});
|
2023-09-01 10:58:27 -07:00
|
|
|
}
|
2024-01-26 19:20:56 -08:00
|
|
|
else if (AstStatWhile* statWhile = (*it)->as<AstStatWhile>(); statWhile && !statWhile->body->hasEnd)
|
|
|
|
result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword});
|
|
|
|
else if (AstExprFunction* exprFunction = (*it)->as<AstExprFunction>(); exprFunction && !exprFunction->body->hasEnd)
|
|
|
|
result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword});
|
|
|
|
if (AstStatBlock* exprBlock = (*it)->as<AstStatBlock>(); exprBlock && !exprBlock->hasEnd)
|
|
|
|
result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword});
|
2021-10-29 13:25:12 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if (ancestry.size() >= 2)
|
|
|
|
{
|
|
|
|
AstNode* parent = ancestry.rbegin()[1];
|
|
|
|
if (AstStatIf* statIf = parent->as<AstStatIf>())
|
|
|
|
{
|
2022-02-17 17:18:01 -08:00
|
|
|
if (!statIf->elsebody || (statIf->elseLocation && statIf->elseLocation->containsClosed(position)))
|
2021-10-29 13:25:12 -07:00
|
|
|
{
|
|
|
|
result.emplace("else", AutocompleteEntry{AutocompleteEntryKind::Keyword});
|
|
|
|
result.emplace("elseif", AutocompleteEntry{AutocompleteEntryKind::Keyword});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-26 19:20:56 -08:00
|
|
|
if (AstStatRepeat* statRepeat = parent->as<AstStatRepeat>(); statRepeat && !statRepeat->body->hasEnd)
|
|
|
|
result.emplace("until", AutocompleteEntry{AutocompleteEntryKind::Keyword});
|
2021-10-29 13:25:12 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if (ancestry.size() >= 4)
|
|
|
|
{
|
|
|
|
auto iter = ancestry.rbegin();
|
|
|
|
if (AstStatIf* statIf = iter[3]->as<AstStatIf>();
|
|
|
|
statIf != nullptr && !statIf->elsebody && iter[2]->is<AstStatBlock>() && iter[1]->is<AstStatError>() && isIdentifier(iter[0]))
|
|
|
|
{
|
|
|
|
result.emplace("else", AutocompleteEntry{AutocompleteEntryKind::Keyword});
|
|
|
|
result.emplace("elseif", AutocompleteEntry{AutocompleteEntryKind::Keyword});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-26 19:20:56 -08:00
|
|
|
if (AstStatRepeat* statRepeat = extractStat<AstStatRepeat>(ancestry); statRepeat && !statRepeat->body->hasEnd)
|
|
|
|
result.emplace("until", AutocompleteEntry{AutocompleteEntryKind::Keyword});
|
2021-10-29 13:25:12 -07:00
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2022-02-17 17:18:01 -08:00
|
|
|
// Returns true iff `node` was handled by this function (completions, if any, are returned in `outResult`)
|
2021-10-29 13:25:12 -07:00
|
|
|
static bool autocompleteIfElseExpression(
|
|
|
|
const AstNode* node, const std::vector<AstNode*>& ancestry, const Position& position, AutocompleteEntryMap& outResult)
|
|
|
|
{
|
|
|
|
AstNode* parent = ancestry.size() >= 2 ? ancestry.rbegin()[1] : nullptr;
|
|
|
|
if (!parent)
|
|
|
|
return false;
|
|
|
|
|
2022-06-16 18:05:14 -07:00
|
|
|
if (node->is<AstExprIfElse>())
|
2022-02-17 17:18:01 -08:00
|
|
|
{
|
|
|
|
// Don't try to complete when the current node is an if-else expression (i.e. only try to complete when the node is a child of an if-else
|
|
|
|
// expression.
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2021-10-29 13:25:12 -07:00
|
|
|
AstExprIfElse* ifElseExpr = parent->as<AstExprIfElse>();
|
|
|
|
if (!ifElseExpr || ifElseExpr->condition->location.containsClosed(position))
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
else if (!ifElseExpr->hasThen)
|
|
|
|
{
|
|
|
|
outResult["then"] = {AutocompleteEntryKind::Keyword};
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else if (ifElseExpr->trueExpr->location.containsClosed(position))
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
else if (!ifElseExpr->hasElse)
|
|
|
|
{
|
|
|
|
outResult["else"] = {AutocompleteEntryKind::Keyword};
|
|
|
|
outResult["elseif"] = {AutocompleteEntryKind::Keyword};
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-04 12:53:17 -08:00
|
|
|
static AutocompleteContext autocompleteExpression(const SourceModule& sourceModule, const Module& module, NotNull<BuiltinTypes> builtinTypes,
|
2022-09-01 16:14:03 -07:00
|
|
|
TypeArena* typeArena, const std::vector<AstNode*>& ancestry, Position position, AutocompleteEntryMap& result)
|
2021-10-29 13:25:12 -07:00
|
|
|
{
|
|
|
|
LUAU_ASSERT(!ancestry.empty());
|
|
|
|
|
|
|
|
AstNode* node = ancestry.rbegin()[0];
|
|
|
|
|
|
|
|
if (node->is<AstExprIndexName>())
|
|
|
|
{
|
2021-11-04 19:34:35 -07:00
|
|
|
if (auto it = module.astTypes.find(node->asExpr()))
|
2023-01-04 12:53:17 -08:00
|
|
|
autocompleteProps(module, typeArena, builtinTypes, *it, PropIndexType::Point, ancestry, result);
|
2021-10-29 13:25:12 -07:00
|
|
|
}
|
2022-01-14 08:20:09 -08:00
|
|
|
else if (autocompleteIfElseExpression(node, ancestry, position, result))
|
2022-08-10 21:04:08 +01:00
|
|
|
return AutocompleteContext::Keyword;
|
2021-10-29 13:25:12 -07:00
|
|
|
else if (node->is<AstExprFunction>())
|
2022-08-10 21:04:08 +01:00
|
|
|
return AutocompleteContext::Unknown;
|
2021-10-29 13:25:12 -07:00
|
|
|
else
|
|
|
|
{
|
|
|
|
// This is inefficient. :(
|
|
|
|
ScopePtr scope = findScopeAtPosition(module, position);
|
|
|
|
|
|
|
|
while (scope)
|
|
|
|
{
|
|
|
|
for (const auto& [name, binding] : scope->bindings)
|
|
|
|
{
|
2022-08-18 14:32:08 -07:00
|
|
|
if (!isBindingLegalAtCurrentPosition(name, binding, position))
|
2021-10-29 13:25:12 -07:00
|
|
|
continue;
|
|
|
|
|
|
|
|
if (isBeingDefined(ancestry, name))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
std::string n = toString(name);
|
|
|
|
if (!result.count(n))
|
|
|
|
{
|
2023-01-04 12:53:17 -08:00
|
|
|
TypeCorrectKind typeCorrect = checkTypeCorrectKind(module, typeArena, builtinTypes, node, position, binding.typeId);
|
2021-10-29 13:25:12 -07:00
|
|
|
|
|
|
|
result[n] = {AutocompleteEntryKind::Binding, binding.typeId, binding.deprecated, false, typeCorrect, std::nullopt, std::nullopt,
|
|
|
|
binding.documentationSymbol, {}, getParenRecommendation(binding.typeId, ancestry, typeCorrect)};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
scope = scope->parent;
|
|
|
|
}
|
|
|
|
|
2023-01-04 12:53:17 -08:00
|
|
|
TypeCorrectKind correctForNil = checkTypeCorrectKind(module, typeArena, builtinTypes, node, position, builtinTypes->nilType);
|
|
|
|
TypeCorrectKind correctForTrue = checkTypeCorrectKind(module, typeArena, builtinTypes, node, position, builtinTypes->trueType);
|
|
|
|
TypeCorrectKind correctForFalse = checkTypeCorrectKind(module, typeArena, builtinTypes, node, position, builtinTypes->falseType);
|
2022-05-05 17:03:43 -07:00
|
|
|
TypeCorrectKind correctForFunction =
|
|
|
|
functionIsExpectedAt(module, node, position).value_or(false) ? TypeCorrectKind::Correct : TypeCorrectKind::None;
|
|
|
|
|
|
|
|
result["if"] = {AutocompleteEntryKind::Keyword, std::nullopt, false, false};
|
2023-01-04 12:53:17 -08:00
|
|
|
result["true"] = {AutocompleteEntryKind::Keyword, builtinTypes->booleanType, false, false, correctForTrue};
|
|
|
|
result["false"] = {AutocompleteEntryKind::Keyword, builtinTypes->booleanType, false, false, correctForFalse};
|
|
|
|
result["nil"] = {AutocompleteEntryKind::Keyword, builtinTypes->nilType, false, false, correctForNil};
|
2022-05-05 17:03:43 -07:00
|
|
|
result["not"] = {AutocompleteEntryKind::Keyword};
|
|
|
|
result["function"] = {AutocompleteEntryKind::Keyword, std::nullopt, false, false, correctForFunction};
|
|
|
|
|
|
|
|
if (auto ty = findExpectedTypeAt(module, node, position))
|
2023-08-25 10:23:55 -07:00
|
|
|
autocompleteStringSingleton(*ty, true, node, position, result);
|
2021-10-29 13:25:12 -07:00
|
|
|
}
|
2022-08-10 21:04:08 +01:00
|
|
|
|
|
|
|
return AutocompleteContext::Expression;
|
2021-10-29 13:25:12 -07:00
|
|
|
}
|
|
|
|
|
2023-01-04 12:53:17 -08:00
|
|
|
static AutocompleteResult autocompleteExpression(const SourceModule& sourceModule, const Module& module, NotNull<BuiltinTypes> builtinTypes,
|
2021-10-29 13:25:12 -07:00
|
|
|
TypeArena* typeArena, const std::vector<AstNode*>& ancestry, Position position)
|
|
|
|
{
|
|
|
|
AutocompleteEntryMap result;
|
2023-01-04 12:53:17 -08:00
|
|
|
AutocompleteContext context = autocompleteExpression(sourceModule, module, builtinTypes, typeArena, ancestry, position, result);
|
2022-08-10 21:04:08 +01:00
|
|
|
return {result, ancestry, context};
|
2021-10-29 13:25:12 -07:00
|
|
|
}
|
|
|
|
|
2023-01-04 12:53:17 -08:00
|
|
|
static std::optional<const ClassType*> getMethodContainingClass(const ModulePtr& module, AstExpr* funcExpr)
|
2021-10-29 13:25:12 -07:00
|
|
|
{
|
|
|
|
AstExpr* parentExpr = nullptr;
|
|
|
|
if (auto indexName = funcExpr->as<AstExprIndexName>())
|
|
|
|
{
|
|
|
|
parentExpr = indexName->expr;
|
|
|
|
}
|
|
|
|
else if (auto indexExpr = funcExpr->as<AstExprIndexExpr>())
|
|
|
|
{
|
|
|
|
parentExpr = indexExpr->expr;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return std::nullopt;
|
|
|
|
}
|
|
|
|
|
2021-11-04 19:34:35 -07:00
|
|
|
auto parentIt = module->astTypes.find(parentExpr);
|
|
|
|
if (!parentIt)
|
2021-10-29 13:25:12 -07:00
|
|
|
{
|
|
|
|
return std::nullopt;
|
|
|
|
}
|
|
|
|
|
2021-11-04 19:34:35 -07:00
|
|
|
Luau::TypeId parentType = Luau::follow(*parentIt);
|
2021-10-29 13:25:12 -07:00
|
|
|
|
2023-01-04 12:53:17 -08:00
|
|
|
if (auto parentClass = Luau::get<ClassType>(parentType))
|
2021-10-29 13:25:12 -07:00
|
|
|
{
|
|
|
|
return parentClass;
|
|
|
|
}
|
|
|
|
|
2023-01-04 12:53:17 -08:00
|
|
|
if (auto parentUnion = Luau::get<UnionType>(parentType))
|
2021-10-29 13:25:12 -07:00
|
|
|
{
|
2023-01-04 12:53:17 -08:00
|
|
|
return returnFirstNonnullOptionOfType<ClassType>(parentUnion);
|
2021-10-29 13:25:12 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
return std::nullopt;
|
|
|
|
}
|
|
|
|
|
2022-11-16 10:15:01 -08:00
|
|
|
static bool stringPartOfInterpString(const AstNode* node, Position position)
|
|
|
|
{
|
|
|
|
const AstExprInterpString* interpString = node->as<AstExprInterpString>();
|
|
|
|
if (!interpString)
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const AstExpr* expression : interpString->expressions)
|
|
|
|
{
|
|
|
|
if (expression->location.containsClosed(position))
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool isSimpleInterpolatedString(const AstNode* node)
|
|
|
|
{
|
|
|
|
const AstExprInterpString* interpString = node->as<AstExprInterpString>();
|
|
|
|
return interpString != nullptr && interpString->expressions.size == 0;
|
|
|
|
}
|
|
|
|
|
2023-01-11 16:28:11 +00:00
|
|
|
static std::optional<std::string> getStringContents(const AstNode* node)
|
|
|
|
{
|
|
|
|
if (const AstExprConstantString* string = node->as<AstExprConstantString>())
|
|
|
|
{
|
|
|
|
return std::string(string->value.data, string->value.size);
|
|
|
|
}
|
|
|
|
else if (const AstExprInterpString* interpString = node->as<AstExprInterpString>(); interpString && interpString->expressions.size == 0)
|
|
|
|
{
|
|
|
|
LUAU_ASSERT(interpString->strings.size == 1);
|
|
|
|
return std::string(interpString->strings.data->data, interpString->strings.data->size);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return std::nullopt;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-29 13:25:12 -07:00
|
|
|
static std::optional<AutocompleteEntryMap> autocompleteStringParams(const SourceModule& sourceModule, const ModulePtr& module,
|
|
|
|
const std::vector<AstNode*>& nodes, Position position, StringCompletionCallback callback)
|
|
|
|
{
|
|
|
|
if (nodes.size() < 2)
|
|
|
|
{
|
|
|
|
return std::nullopt;
|
|
|
|
}
|
|
|
|
|
2022-11-16 10:15:01 -08:00
|
|
|
if (!nodes.back()->is<AstExprConstantString>() && !isSimpleInterpolatedString(nodes.back()) && !nodes.back()->is<AstExprError>())
|
2021-10-29 13:25:12 -07:00
|
|
|
{
|
|
|
|
return std::nullopt;
|
|
|
|
}
|
|
|
|
|
2023-08-18 11:15:41 -07:00
|
|
|
if (!nodes.back()->is<AstExprError>())
|
2023-07-28 08:13:53 -07:00
|
|
|
{
|
|
|
|
if (nodes.back()->location.end == position || nodes.back()->location.begin == position)
|
|
|
|
{
|
|
|
|
return std::nullopt;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-29 13:25:12 -07:00
|
|
|
AstExprCall* candidate = nodes.at(nodes.size() - 2)->as<AstExprCall>();
|
|
|
|
if (!candidate)
|
|
|
|
{
|
|
|
|
return std::nullopt;
|
|
|
|
}
|
|
|
|
|
|
|
|
// HACK: All current instances of 'magic string' params are the first parameter of their functions,
|
2023-01-04 12:53:17 -08:00
|
|
|
// so we encode that here rather than putting a useless member on the FunctionType struct.
|
2021-10-29 13:25:12 -07:00
|
|
|
if (candidate->args.size > 1 && !candidate->args.data[0]->location.contains(position))
|
|
|
|
{
|
|
|
|
return std::nullopt;
|
|
|
|
}
|
|
|
|
|
2021-11-04 19:34:35 -07:00
|
|
|
auto it = module->astTypes.find(candidate->func);
|
|
|
|
if (!it)
|
2021-10-29 13:25:12 -07:00
|
|
|
{
|
|
|
|
return std::nullopt;
|
|
|
|
}
|
|
|
|
|
2023-01-11 16:28:11 +00:00
|
|
|
std::optional<std::string> candidateString = getStringContents(nodes.back());
|
|
|
|
|
2023-01-20 12:27:03 -08:00
|
|
|
auto performCallback = [&](const FunctionType* funcType) -> std::optional<AutocompleteEntryMap> {
|
2021-10-29 13:25:12 -07:00
|
|
|
for (const std::string& tag : funcType->tags)
|
|
|
|
{
|
2023-01-11 16:28:11 +00:00
|
|
|
if (std::optional<AutocompleteEntryMap> ret = callback(tag, getMethodContainingClass(module, candidate->func), candidateString))
|
2021-10-29 13:25:12 -07:00
|
|
|
{
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return std::nullopt;
|
|
|
|
};
|
|
|
|
|
2021-11-04 19:34:35 -07:00
|
|
|
auto followedId = Luau::follow(*it);
|
2023-01-04 12:53:17 -08:00
|
|
|
if (auto functionType = Luau::get<FunctionType>(followedId))
|
2021-10-29 13:25:12 -07:00
|
|
|
{
|
|
|
|
return performCallback(functionType);
|
|
|
|
}
|
|
|
|
|
2023-01-04 12:53:17 -08:00
|
|
|
if (auto intersect = Luau::get<IntersectionType>(followedId))
|
2021-10-29 13:25:12 -07:00
|
|
|
{
|
|
|
|
for (TypeId part : intersect->parts)
|
|
|
|
{
|
2023-01-04 12:53:17 -08:00
|
|
|
if (auto candidateFunctionType = Luau::get<FunctionType>(part))
|
2021-10-29 13:25:12 -07:00
|
|
|
{
|
|
|
|
if (std::optional<AutocompleteEntryMap> ret = performCallback(candidateFunctionType))
|
|
|
|
{
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return std::nullopt;
|
|
|
|
}
|
|
|
|
|
2023-01-20 12:27:03 -08:00
|
|
|
static AutocompleteResult autocompleteWhileLoopKeywords(std::vector<AstNode*> ancestry)
|
|
|
|
{
|
|
|
|
AutocompleteEntryMap ret;
|
|
|
|
ret["do"] = {AutocompleteEntryKind::Keyword};
|
|
|
|
ret["and"] = {AutocompleteEntryKind::Keyword};
|
|
|
|
ret["or"] = {AutocompleteEntryKind::Keyword};
|
|
|
|
return {std::move(ret), std::move(ancestry), AutocompleteContext::Keyword};
|
|
|
|
}
|
|
|
|
|
2023-07-28 08:13:53 -07:00
|
|
|
static std::string makeAnonymous(const ScopePtr& scope, const FunctionType& funcTy)
|
|
|
|
{
|
|
|
|
std::string result = "function(";
|
|
|
|
|
|
|
|
auto [args, tail] = Luau::flatten(funcTy.argTypes);
|
|
|
|
|
|
|
|
bool first = true;
|
|
|
|
// Skip the implicit 'self' argument if call is indexed with ':'
|
|
|
|
for (size_t argIdx = 0; argIdx < args.size(); ++argIdx)
|
|
|
|
{
|
|
|
|
if (!first)
|
|
|
|
result += ", ";
|
|
|
|
else
|
|
|
|
first = false;
|
|
|
|
|
|
|
|
std::string name;
|
|
|
|
if (argIdx < funcTy.argNames.size() && funcTy.argNames[argIdx])
|
|
|
|
name = funcTy.argNames[argIdx]->name;
|
|
|
|
else
|
|
|
|
name = "a" + std::to_string(argIdx);
|
|
|
|
|
|
|
|
if (std::optional<Name> type = tryGetTypeNameInScope(scope, args[argIdx], true))
|
2023-09-15 10:26:59 -07:00
|
|
|
result += name + ": " + *type;
|
2023-07-28 08:13:53 -07:00
|
|
|
else
|
|
|
|
result += name;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (tail && (Luau::isVariadic(*tail) || Luau::get<Luau::FreeTypePack>(Luau::follow(*tail))))
|
|
|
|
{
|
|
|
|
if (!first)
|
|
|
|
result += ", ";
|
|
|
|
|
|
|
|
std::optional<std::string> varArgType;
|
|
|
|
if (const VariadicTypePack* pack = get<VariadicTypePack>(follow(*tail)))
|
|
|
|
{
|
|
|
|
if (std::optional<std::string> res = tryToStringDetailed(scope, pack->ty, true))
|
|
|
|
varArgType = std::move(res);
|
|
|
|
}
|
2023-09-15 10:26:59 -07:00
|
|
|
|
2023-07-28 08:13:53 -07:00
|
|
|
if (varArgType)
|
|
|
|
result += "...: " + *varArgType;
|
|
|
|
else
|
|
|
|
result += "...";
|
|
|
|
}
|
|
|
|
|
|
|
|
result += ")";
|
|
|
|
|
|
|
|
auto [rets, retTail] = Luau::flatten(funcTy.retTypes);
|
|
|
|
if (const size_t totalRetSize = rets.size() + (retTail ? 1 : 0); totalRetSize > 0)
|
|
|
|
{
|
|
|
|
if (std::optional<std::string> returnTypes = tryToStringDetailed(scope, funcTy.retTypes, true))
|
|
|
|
{
|
|
|
|
result += ": ";
|
|
|
|
bool wrap = totalRetSize != 1;
|
|
|
|
if (wrap)
|
|
|
|
result += "(";
|
|
|
|
result += *returnTypes;
|
|
|
|
if (wrap)
|
|
|
|
result += ")";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
result += " end";
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2023-09-15 10:26:59 -07:00
|
|
|
static std::optional<AutocompleteEntry> makeAnonymousAutofilled(
|
|
|
|
const ModulePtr& module, Position position, const AstNode* node, const std::vector<AstNode*>& ancestry)
|
2023-07-28 08:13:53 -07:00
|
|
|
{
|
|
|
|
const AstExprCall* call = node->as<AstExprCall>();
|
|
|
|
if (!call && ancestry.size() > 1)
|
|
|
|
call = ancestry[ancestry.size() - 2]->as<AstExprCall>();
|
|
|
|
|
|
|
|
if (!call)
|
|
|
|
return std::nullopt;
|
|
|
|
|
|
|
|
if (!call->location.containsClosed(position) || call->func->location.containsClosed(position))
|
|
|
|
return std::nullopt;
|
|
|
|
|
|
|
|
TypeId* typeIter = module->astTypes.find(call->func);
|
|
|
|
if (!typeIter)
|
|
|
|
return std::nullopt;
|
|
|
|
|
|
|
|
const FunctionType* outerFunction = get<FunctionType>(follow(*typeIter));
|
|
|
|
if (!outerFunction)
|
|
|
|
return std::nullopt;
|
|
|
|
|
|
|
|
size_t argument = 0;
|
|
|
|
for (size_t i = 0; i < call->args.size; ++i)
|
|
|
|
{
|
|
|
|
if (call->args.data[i]->location.containsClosed(position))
|
|
|
|
{
|
|
|
|
argument = i;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (call->self)
|
|
|
|
argument++;
|
|
|
|
|
|
|
|
std::optional<TypeId> argType;
|
|
|
|
auto [args, tail] = flatten(outerFunction->argTypes);
|
|
|
|
if (argument < args.size())
|
|
|
|
argType = args[argument];
|
2023-09-15 10:26:59 -07:00
|
|
|
|
2023-07-28 08:13:53 -07:00
|
|
|
if (!argType)
|
|
|
|
return std::nullopt;
|
2023-09-15 10:26:59 -07:00
|
|
|
|
2023-07-28 08:13:53 -07:00
|
|
|
TypeId followed = follow(*argType);
|
|
|
|
const FunctionType* type = get<FunctionType>(followed);
|
|
|
|
if (!type)
|
|
|
|
{
|
|
|
|
if (const UnionType* unionType = get<UnionType>(followed))
|
|
|
|
{
|
|
|
|
if (std::optional<const FunctionType*> nonnullFunction = returnFirstNonnullOptionOfType<FunctionType>(unionType))
|
|
|
|
type = *nonnullFunction;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!type)
|
|
|
|
return std::nullopt;
|
|
|
|
|
|
|
|
const ScopePtr scope = findScopeAtPosition(*module, position);
|
|
|
|
if (!scope)
|
|
|
|
return std::nullopt;
|
|
|
|
|
|
|
|
AutocompleteEntry entry;
|
|
|
|
entry.kind = AutocompleteEntryKind::GeneratedFunction;
|
|
|
|
entry.typeCorrect = TypeCorrectKind::Correct;
|
|
|
|
entry.type = argType;
|
|
|
|
entry.insertText = makeAnonymous(scope, *type);
|
|
|
|
return std::make_optional(std::move(entry));
|
|
|
|
}
|
|
|
|
|
2023-01-04 12:53:17 -08:00
|
|
|
static AutocompleteResult autocomplete(const SourceModule& sourceModule, const ModulePtr& module, NotNull<BuiltinTypes> builtinTypes,
|
2023-01-06 13:14:35 -08:00
|
|
|
TypeArena* typeArena, Scope* globalScope, Position position, StringCompletionCallback callback)
|
2021-10-29 13:25:12 -07:00
|
|
|
{
|
|
|
|
if (isWithinComment(sourceModule, position))
|
|
|
|
return {};
|
|
|
|
|
2022-07-07 18:22:39 -07:00
|
|
|
std::vector<AstNode*> ancestry = findAncestryAtPositionForAutocomplete(sourceModule, position);
|
|
|
|
LUAU_ASSERT(!ancestry.empty());
|
|
|
|
AstNode* node = ancestry.back();
|
2021-10-29 13:25:12 -07:00
|
|
|
|
|
|
|
AstExprConstantNil dummy{Location{}};
|
2022-07-07 18:22:39 -07:00
|
|
|
AstNode* parent = ancestry.size() >= 2 ? ancestry.rbegin()[1] : &dummy;
|
2021-10-29 13:25:12 -07:00
|
|
|
|
|
|
|
// If we are inside a body of a function that doesn't have a completed argument list, ignore the body node
|
|
|
|
if (auto exprFunction = parent->as<AstExprFunction>(); exprFunction && !exprFunction->argLocation && node == exprFunction->body)
|
|
|
|
{
|
2022-07-07 18:22:39 -07:00
|
|
|
ancestry.pop_back();
|
2021-10-29 13:25:12 -07:00
|
|
|
|
2022-07-07 18:22:39 -07:00
|
|
|
node = ancestry.back();
|
|
|
|
parent = ancestry.size() >= 2 ? ancestry.rbegin()[1] : &dummy;
|
2021-10-29 13:25:12 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if (auto indexName = node->as<AstExprIndexName>())
|
|
|
|
{
|
|
|
|
auto it = module->astTypes.find(indexName->expr);
|
2021-11-04 19:34:35 -07:00
|
|
|
if (!it)
|
2021-10-29 13:25:12 -07:00
|
|
|
return {};
|
|
|
|
|
2021-11-04 19:34:35 -07:00
|
|
|
TypeId ty = follow(*it);
|
2021-10-29 13:25:12 -07:00
|
|
|
PropIndexType indexType = indexName->op == ':' ? PropIndexType::Colon : PropIndexType::Point;
|
|
|
|
|
2023-01-06 13:14:35 -08:00
|
|
|
return {autocompleteProps(*module, typeArena, builtinTypes, ty, indexType, ancestry), ancestry, AutocompleteContext::Property};
|
2021-10-29 13:25:12 -07:00
|
|
|
}
|
|
|
|
else if (auto typeReference = node->as<AstTypeReference>())
|
|
|
|
{
|
2022-02-17 17:18:01 -08:00
|
|
|
if (typeReference->prefix)
|
2022-08-10 21:04:08 +01:00
|
|
|
return {autocompleteModuleTypes(*module, position, typeReference->prefix->value), ancestry, AutocompleteContext::Type};
|
2021-10-29 13:25:12 -07:00
|
|
|
else
|
2022-08-10 21:04:08 +01:00
|
|
|
return {autocompleteTypeNames(*module, position, ancestry), ancestry, AutocompleteContext::Type};
|
2021-10-29 13:25:12 -07:00
|
|
|
}
|
|
|
|
else if (node->is<AstTypeError>())
|
|
|
|
{
|
2022-08-10 21:04:08 +01:00
|
|
|
return {autocompleteTypeNames(*module, position, ancestry), ancestry, AutocompleteContext::Type};
|
2021-10-29 13:25:12 -07:00
|
|
|
}
|
|
|
|
else if (AstStatLocal* statLocal = node->as<AstStatLocal>())
|
|
|
|
{
|
2022-02-17 17:18:01 -08:00
|
|
|
if (statLocal->vars.size == 1 && (!statLocal->equalsSignLocation || position < statLocal->equalsSignLocation->begin))
|
2022-08-10 21:04:08 +01:00
|
|
|
return {{{"function", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry, AutocompleteContext::Unknown};
|
2022-02-17 17:18:01 -08:00
|
|
|
else if (statLocal->equalsSignLocation && position >= statLocal->equalsSignLocation->end)
|
2023-01-06 13:14:35 -08:00
|
|
|
return autocompleteExpression(sourceModule, *module, builtinTypes, typeArena, ancestry, position);
|
2021-10-29 13:25:12 -07:00
|
|
|
else
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
2022-07-07 18:22:39 -07:00
|
|
|
else if (AstStatFor* statFor = extractStat<AstStatFor>(ancestry))
|
2021-10-29 13:25:12 -07:00
|
|
|
{
|
|
|
|
if (!statFor->hasDo || position < statFor->doLocation.begin)
|
|
|
|
{
|
2023-03-10 12:21:07 -08:00
|
|
|
if (statFor->from->location.containsClosed(position) || statFor->to->location.containsClosed(position) ||
|
|
|
|
(statFor->step && statFor->step->location.containsClosed(position)))
|
|
|
|
return autocompleteExpression(sourceModule, *module, builtinTypes, typeArena, ancestry, position);
|
2021-10-29 13:25:12 -07:00
|
|
|
|
2023-03-10 12:21:07 -08:00
|
|
|
if (!statFor->from->is<AstExprError>() && !statFor->to->is<AstExprError>() && (!statFor->step || !statFor->step->is<AstExprError>()))
|
|
|
|
return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry, AutocompleteContext::Keyword};
|
2021-10-29 13:25:12 -07:00
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
2022-08-10 21:04:08 +01:00
|
|
|
return {autocompleteStatement(sourceModule, *module, ancestry, position), ancestry, AutocompleteContext::Statement};
|
2021-10-29 13:25:12 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
else if (AstStatForIn* statForIn = parent->as<AstStatForIn>(); statForIn && (node->is<AstStatBlock>() || isIdentifier(node)))
|
|
|
|
{
|
|
|
|
if (!statForIn->hasIn || position <= statForIn->inLocation.begin)
|
|
|
|
{
|
|
|
|
AstLocal* lastName = statForIn->vars.data[statForIn->vars.size - 1];
|
2022-02-17 17:18:01 -08:00
|
|
|
if (lastName->name == kParseNameError || lastName->location.containsClosed(position))
|
2021-10-29 13:25:12 -07:00
|
|
|
{
|
|
|
|
// Here we are either working with a missing binding (as would be the case in a bare "for" keyword) or
|
|
|
|
// the cursor is still touching a binding name. The user is still typing a new name, so we should not offer
|
|
|
|
// any suggestions.
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
2022-08-10 21:04:08 +01:00
|
|
|
return {{{"in", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry, AutocompleteContext::Keyword};
|
2021-10-29 13:25:12 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!statForIn->hasDo || position <= statForIn->doLocation.begin)
|
|
|
|
{
|
|
|
|
LUAU_ASSERT(statForIn->values.size > 0);
|
|
|
|
AstExpr* lastExpr = statForIn->values.data[statForIn->values.size - 1];
|
|
|
|
|
|
|
|
if (lastExpr->location.containsClosed(position))
|
2023-01-06 13:14:35 -08:00
|
|
|
return autocompleteExpression(sourceModule, *module, builtinTypes, typeArena, ancestry, position);
|
2021-10-29 13:25:12 -07:00
|
|
|
|
|
|
|
if (position > lastExpr->location.end)
|
2022-08-10 21:04:08 +01:00
|
|
|
return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry, AutocompleteContext::Keyword};
|
2021-10-29 13:25:12 -07:00
|
|
|
|
|
|
|
return {}; // Not sure what this means
|
|
|
|
}
|
|
|
|
}
|
2022-07-07 18:22:39 -07:00
|
|
|
else if (AstStatForIn* statForIn = extractStat<AstStatForIn>(ancestry))
|
2021-10-29 13:25:12 -07:00
|
|
|
{
|
|
|
|
// The AST looks a bit differently if the cursor is at a position where only the "do" keyword is allowed.
|
|
|
|
// ex "for f in f do"
|
|
|
|
if (!statForIn->hasDo)
|
2022-08-10 21:04:08 +01:00
|
|
|
return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry, AutocompleteContext::Keyword};
|
2021-10-29 13:25:12 -07:00
|
|
|
|
2022-08-10 21:04:08 +01:00
|
|
|
return {autocompleteStatement(sourceModule, *module, ancestry, position), ancestry, AutocompleteContext::Statement};
|
2021-10-29 13:25:12 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
else if (AstStatWhile* statWhile = parent->as<AstStatWhile>(); node->is<AstStatBlock>() && statWhile)
|
|
|
|
{
|
|
|
|
if (!statWhile->hasDo && !statWhile->condition->is<AstStatError>() && position > statWhile->condition->location.end)
|
2023-01-20 12:27:03 -08:00
|
|
|
{
|
2023-03-10 12:21:07 -08:00
|
|
|
return autocompleteWhileLoopKeywords(ancestry);
|
2023-01-20 12:27:03 -08:00
|
|
|
}
|
2021-10-29 13:25:12 -07:00
|
|
|
|
|
|
|
if (!statWhile->hasDo || position < statWhile->doLocation.begin)
|
2023-01-06 13:14:35 -08:00
|
|
|
return autocompleteExpression(sourceModule, *module, builtinTypes, typeArena, ancestry, position);
|
2021-10-29 13:25:12 -07:00
|
|
|
|
|
|
|
if (statWhile->hasDo && position > statWhile->doLocation.end)
|
2022-08-10 21:04:08 +01:00
|
|
|
return {autocompleteStatement(sourceModule, *module, ancestry, position), ancestry, AutocompleteContext::Statement};
|
2021-10-29 13:25:12 -07:00
|
|
|
}
|
|
|
|
|
2023-01-20 12:27:03 -08:00
|
|
|
else if (AstStatWhile* statWhile = extractStat<AstStatWhile>(ancestry);
|
2023-03-10 12:21:07 -08:00
|
|
|
(statWhile && (!statWhile->hasDo || statWhile->doLocation.containsClosed(position)) && statWhile->condition &&
|
|
|
|
!statWhile->condition->location.containsClosed(position)))
|
2023-01-20 12:27:03 -08:00
|
|
|
{
|
2023-03-10 12:21:07 -08:00
|
|
|
return autocompleteWhileLoopKeywords(ancestry);
|
2023-01-20 12:27:03 -08:00
|
|
|
}
|
2022-02-17 17:18:01 -08:00
|
|
|
else if (AstStatIf* statIf = node->as<AstStatIf>(); statIf && !statIf->elseLocation.has_value())
|
2021-10-29 13:25:12 -07:00
|
|
|
{
|
2022-09-01 16:14:03 -07:00
|
|
|
return {{{"else", AutocompleteEntry{AutocompleteEntryKind::Keyword}}, {"elseif", AutocompleteEntry{AutocompleteEntryKind::Keyword}}},
|
|
|
|
ancestry, AutocompleteContext::Keyword};
|
2021-10-29 13:25:12 -07:00
|
|
|
}
|
|
|
|
else if (AstStatIf* statIf = parent->as<AstStatIf>(); statIf && node->is<AstStatBlock>())
|
|
|
|
{
|
|
|
|
if (statIf->condition->is<AstExprError>())
|
2023-01-06 13:14:35 -08:00
|
|
|
return autocompleteExpression(sourceModule, *module, builtinTypes, typeArena, ancestry, position);
|
2022-02-17 17:18:01 -08:00
|
|
|
else if (!statIf->thenLocation || statIf->thenLocation->containsClosed(position))
|
2022-08-10 21:04:08 +01:00
|
|
|
return {{{"then", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry, AutocompleteContext::Keyword};
|
2021-10-29 13:25:12 -07:00
|
|
|
}
|
2023-03-03 22:21:14 +02:00
|
|
|
else if (AstStatIf* statIf = extractStat<AstStatIf>(ancestry); statIf &&
|
|
|
|
(!statIf->thenLocation || statIf->thenLocation->containsClosed(position)) &&
|
|
|
|
(statIf->condition && !statIf->condition->location.containsClosed(position)))
|
2023-01-13 14:10:01 -08:00
|
|
|
{
|
Sync to upstream/release/562 (#828)
* Fixed rare use-after-free in analysis during table unification
A lot of work these past months went into two new Luau components:
* A near full rewrite of the typechecker using a new deferred constraint
resolution system
* Native code generation for AoT/JiT compilation of VM bytecode into x64
(avx)/arm64 instructions
Both of these components are far from finished and we don't provide
documentation on building and using them at this point.
However, curious community members expressed interest in learning about
changes that go into these components each week, so we are now listing
them here in the 'sync' pull request descriptions.
---
New typechecker can be enabled by setting
DebugLuauDeferredConstraintResolution flag to 'true'.
It is considered unstable right now, so try it at your own risk.
Even though it already provides better type inference than the current
one in some cases, our main goal right now is to reach feature parity
with current typechecker.
Features which improve over the capabilities of the current typechecker
are marked as '(NEW)'.
Changes to new typechecker:
* Regular for loop index and parameters are now typechecked
* Invalid type annotations on local variables are ignored to improve
autocomplete
* Fixed missing autocomplete type suggestions for function arguments
* Type reduction is now performed to produce simpler types to be
presented to the user (error messages, custom LSPs)
* Internally, complex types like '((number | string) & ~(false?)) |
string' can be produced, which is just 'string | number' when simplified
* Fixed spots where support for unknown and never types was missing
* (NEW) Length operator '#' is now valid to use on top table type, this
type comes up when doing typeof(x) == "table" guards and isn't available
in current typechecker
---
Changes to native code generation:
* Additional math library fast calls are now lowered to x64: math.ldexp,
math.round, math.frexp, math.modf, math.sign and math.clamp
2023-02-03 21:26:13 +02:00
|
|
|
AutocompleteEntryMap ret;
|
|
|
|
ret["then"] = {AutocompleteEntryKind::Keyword};
|
|
|
|
ret["and"] = {AutocompleteEntryKind::Keyword};
|
|
|
|
ret["or"] = {AutocompleteEntryKind::Keyword};
|
|
|
|
return {std::move(ret), ancestry, AutocompleteContext::Keyword};
|
2023-01-13 14:10:01 -08:00
|
|
|
}
|
2021-10-29 13:25:12 -07:00
|
|
|
else if (AstStatRepeat* statRepeat = node->as<AstStatRepeat>(); statRepeat && statRepeat->condition->is<AstExprError>())
|
2023-01-06 13:14:35 -08:00
|
|
|
return autocompleteExpression(sourceModule, *module, builtinTypes, typeArena, ancestry, position);
|
2022-07-07 18:22:39 -07:00
|
|
|
else if (AstStatRepeat* statRepeat = extractStat<AstStatRepeat>(ancestry); statRepeat)
|
2022-08-10 21:04:08 +01:00
|
|
|
return {autocompleteStatement(sourceModule, *module, ancestry, position), ancestry, AutocompleteContext::Statement};
|
2022-12-02 10:09:59 -08:00
|
|
|
else if (AstExprTable* exprTable = parent->as<AstExprTable>();
|
|
|
|
exprTable && (node->is<AstExprGlobal>() || node->is<AstExprConstantString>() || node->is<AstExprInterpString>()))
|
2021-10-29 13:25:12 -07:00
|
|
|
{
|
|
|
|
for (const auto& [kind, key, value] : exprTable->items)
|
|
|
|
{
|
|
|
|
// If item doesn't have a key, maybe the value is actually the key
|
|
|
|
if (key ? key == node : node->is<AstExprGlobal>() && value == node)
|
|
|
|
{
|
2021-11-04 19:34:35 -07:00
|
|
|
if (auto it = module->astExpectedTypes.find(exprTable))
|
2021-10-29 13:25:12 -07:00
|
|
|
{
|
2023-01-06 13:14:35 -08:00
|
|
|
auto result = autocompleteProps(*module, typeArena, builtinTypes, *it, PropIndexType::Key, ancestry);
|
2021-10-29 13:25:12 -07:00
|
|
|
|
2023-03-31 21:42:49 +03:00
|
|
|
if (auto nodeIt = module->astExpectedTypes.find(node->asExpr()))
|
2023-08-25 10:23:55 -07:00
|
|
|
autocompleteStringSingleton(*nodeIt, !node->is<AstExprConstantString>(), node, position, result);
|
2022-12-09 11:57:01 -08:00
|
|
|
|
2023-03-31 21:42:49 +03:00
|
|
|
if (!key)
|
|
|
|
{
|
|
|
|
// If there is "no key," it may be that the user
|
|
|
|
// intends for the current token to be the key, but
|
|
|
|
// has yet to type the `=` sign.
|
|
|
|
//
|
|
|
|
// If the key type is a union of singleton strings,
|
|
|
|
// suggest those too.
|
|
|
|
if (auto ttv = get<TableType>(follow(*it)); ttv && ttv->indexer)
|
2022-12-09 11:57:01 -08:00
|
|
|
{
|
2023-08-25 10:23:55 -07:00
|
|
|
autocompleteStringSingleton(ttv->indexer->indexType, false, node, position, result);
|
2022-12-09 11:57:01 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-29 13:25:12 -07:00
|
|
|
// Remove keys that are already completed
|
|
|
|
for (const auto& item : exprTable->items)
|
|
|
|
{
|
|
|
|
if (!item.key)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (auto stringKey = item.key->as<AstExprConstantString>())
|
|
|
|
result.erase(std::string(stringKey->value.data, stringKey->value.size));
|
|
|
|
}
|
|
|
|
|
2021-11-04 10:50:46 -04:00
|
|
|
// If we know for sure that a key is being written, do not offer general expression suggestions
|
2021-10-29 13:25:12 -07:00
|
|
|
if (!key)
|
2023-01-06 13:14:35 -08:00
|
|
|
autocompleteExpression(sourceModule, *module, builtinTypes, typeArena, ancestry, position, result);
|
2021-10-29 13:25:12 -07:00
|
|
|
|
2022-08-10 21:04:08 +01:00
|
|
|
return {result, ancestry, AutocompleteContext::Property};
|
2021-10-29 13:25:12 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (isIdentifier(node) && (parent->is<AstStatExpr>() || parent->is<AstStatError>()))
|
2022-08-10 21:04:08 +01:00
|
|
|
return {autocompleteStatement(sourceModule, *module, ancestry, position), ancestry, AutocompleteContext::Statement};
|
2021-10-29 13:25:12 -07:00
|
|
|
|
2022-07-07 18:22:39 -07:00
|
|
|
if (std::optional<AutocompleteEntryMap> ret = autocompleteStringParams(sourceModule, module, ancestry, position, callback))
|
2021-10-29 13:25:12 -07:00
|
|
|
{
|
2022-08-10 21:04:08 +01:00
|
|
|
return {*ret, ancestry, AutocompleteContext::String};
|
2021-10-29 13:25:12 -07:00
|
|
|
}
|
2022-11-16 10:15:01 -08:00
|
|
|
else if (node->is<AstExprConstantString>() || isSimpleInterpolatedString(node))
|
2021-10-29 13:25:12 -07:00
|
|
|
{
|
2022-04-07 14:29:01 -07:00
|
|
|
AutocompleteEntryMap result;
|
|
|
|
|
2022-05-05 17:03:43 -07:00
|
|
|
if (auto it = module->astExpectedTypes.find(node->asExpr()))
|
2023-08-25 10:23:55 -07:00
|
|
|
autocompleteStringSingleton(*it, false, node, position, result);
|
2022-04-07 14:29:01 -07:00
|
|
|
|
2022-07-07 18:22:39 -07:00
|
|
|
if (ancestry.size() >= 2)
|
2021-10-29 13:25:12 -07:00
|
|
|
{
|
2022-07-07 18:22:39 -07:00
|
|
|
if (auto idxExpr = ancestry.at(ancestry.size() - 2)->as<AstExprIndexExpr>())
|
2021-10-29 13:25:12 -07:00
|
|
|
{
|
2021-11-04 19:34:35 -07:00
|
|
|
if (auto it = module->astTypes.find(idxExpr->expr))
|
2023-01-06 13:14:35 -08:00
|
|
|
autocompleteProps(*module, typeArena, builtinTypes, follow(*it), PropIndexType::Point, ancestry, result);
|
2022-04-07 14:29:01 -07:00
|
|
|
}
|
2022-07-07 18:22:39 -07:00
|
|
|
else if (auto binExpr = ancestry.at(ancestry.size() - 2)->as<AstExprBinary>())
|
2022-04-07 14:29:01 -07:00
|
|
|
{
|
|
|
|
if (binExpr->op == AstExprBinary::CompareEq || binExpr->op == AstExprBinary::CompareNe)
|
2021-10-29 13:25:12 -07:00
|
|
|
{
|
2022-04-07 14:29:01 -07:00
|
|
|
if (auto it = module->astTypes.find(node == binExpr->left ? binExpr->right : binExpr->left))
|
2023-08-25 10:23:55 -07:00
|
|
|
autocompleteStringSingleton(*it, false, node, position, result);
|
2021-10-29 13:25:12 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-04-07 14:29:01 -07:00
|
|
|
|
2022-08-10 21:04:08 +01:00
|
|
|
return {result, ancestry, AutocompleteContext::String};
|
2021-10-29 13:25:12 -07:00
|
|
|
}
|
2022-11-16 10:15:01 -08:00
|
|
|
else if (stringPartOfInterpString(node, position))
|
|
|
|
{
|
|
|
|
// We're not a simple interpolated string, we're something like `a{"b"}@1`, and we
|
|
|
|
// can't know what to format to
|
|
|
|
AutocompleteEntryMap map;
|
|
|
|
return {map, ancestry, AutocompleteContext::String};
|
|
|
|
}
|
2021-10-29 13:25:12 -07:00
|
|
|
|
|
|
|
if (node->is<AstExprConstantNumber>())
|
|
|
|
return {};
|
|
|
|
|
|
|
|
if (node->asExpr())
|
2023-07-28 08:13:53 -07:00
|
|
|
{
|
2023-09-07 17:13:49 -07:00
|
|
|
AutocompleteResult ret = autocompleteExpression(sourceModule, *module, builtinTypes, typeArena, ancestry, position);
|
|
|
|
if (std::optional<AutocompleteEntry> generated = makeAnonymousAutofilled(module, position, node, ancestry))
|
|
|
|
ret.entryMap[kGeneratedAnonymousFunctionEntryName] = std::move(*generated);
|
|
|
|
return ret;
|
2023-07-28 08:13:53 -07:00
|
|
|
}
|
2021-10-29 13:25:12 -07:00
|
|
|
else if (node->asStat())
|
2022-08-10 21:04:08 +01:00
|
|
|
return {autocompleteStatement(sourceModule, *module, ancestry, position), ancestry, AutocompleteContext::Statement};
|
2021-10-29 13:25:12 -07:00
|
|
|
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
AutocompleteResult autocomplete(Frontend& frontend, const ModuleName& moduleName, Position position, StringCompletionCallback callback)
|
|
|
|
{
|
|
|
|
const SourceModule* sourceModule = frontend.getSourceModule(moduleName);
|
|
|
|
if (!sourceModule)
|
|
|
|
return {};
|
|
|
|
|
2022-05-26 15:08:16 -07:00
|
|
|
ModulePtr module = frontend.moduleResolverForAutocomplete.getModule(moduleName);
|
2021-10-29 13:25:12 -07:00
|
|
|
if (!module)
|
|
|
|
return {};
|
|
|
|
|
2023-01-04 12:53:17 -08:00
|
|
|
NotNull<BuiltinTypes> builtinTypes = frontend.builtinTypes;
|
2023-03-10 12:21:07 -08:00
|
|
|
Scope* globalScope = frontend.globalsForAutocomplete.globalScope.get();
|
2021-10-29 13:25:12 -07:00
|
|
|
|
2023-01-06 13:14:35 -08:00
|
|
|
TypeArena typeArena;
|
|
|
|
return autocomplete(*sourceModule, module, builtinTypes, &typeArena, globalScope, position, callback);
|
2021-10-29 13:25:12 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace Luau
|