luau/Analysis/src/Autocomplete.cpp

1851 lines
65 KiB
C++
Raw Normal View History

// 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"
#include "Luau/Subtyping.h"
#include "Luau/TypeInfer.h"
#include "Luau/TypePack.h"
#include <algorithm>
#include <unordered_set>
#include <utility>
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
LUAU_FASTFLAG(DebugLuauReadWriteProperties);
LUAU_FASTFLAGVARIABLE(LuauAutocompleteStringLiteralBounds, false);
LUAU_FASTFLAGVARIABLE(LuauAutocompleteTableKeysNoInitialCharacter, false);
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;
}
static ParenthesesRecommendation getParenRecommendationForFunc(const FunctionType* func, const std::vector<AstNode*>& nodes)
{
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);
return noArgFunction ? ParenthesesRecommendation::CursorAfter : ParenthesesRecommendation::CursorInside;
}
static ParenthesesRecommendation getParenRecommendationForIntersect(const IntersectionType* intersect, const std::vector<AstNode*>& nodes)
{
ParenthesesRecommendation rec = ParenthesesRecommendation::None;
for (Luau::TypeId partId : intersect->parts)
{
if (auto partFunc = Luau::get<FunctionType>(partId))
{
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);
if (auto func = get<FunctionType>(id))
{
return getParenRecommendationForFunc(func, nodes);
}
else if (auto intersect = get<IntersectionType>(id))
{
return getParenRecommendationForIntersect(intersect, nodes);
}
return ParenthesesRecommendation::None;
}
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;
const FunctionType* ftv = get<FunctionType>(follow(*it));
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;
}
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);
Normalizer normalizer{typeArena, builtinTypes, NotNull{&unifierState}};
2022-03-17 17:46: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();
}
2022-03-17 17:46:04 -07:00
}
static TypeCorrectKind checkTypeCorrectKind(
const Module& module, TypeArena* typeArena, NotNull<BuiltinTypes> builtinTypes, AstNode* node, Position position, TypeId ty)
{
ty = follow(ty);
LUAU_ASSERT(module.hasModuleScope());
NotNull<Scope> moduleScope{module.getModuleScope().get()};
auto typeAtPosition = findExpectedTypeAt(module, node, position);
if (!typeAtPosition)
return TypeCorrectKind::None;
TypeId expectedType = follow(*typeAtPosition);
auto checkFunctionType = [typeArena, builtinTypes, moduleScope, &expectedType](const FunctionType* ftv) {
if (std::optional<TypeId> firstRetTy = first(ftv->retTypes))
return checkTypeMatch(*firstRetTy, expectedType, moduleScope, typeArena, builtinTypes);
2022-03-17 17:46:04 -07:00
return false;
2022-03-04 08:36:33 -08:00
};
// We also want to suggest functions that return compatible result
if (const FunctionType* ftv = get<FunctionType>(ty); ftv && checkFunctionType(ftv))
2022-03-04 08:36:33 -08:00
{
return TypeCorrectKind::CorrectFunctionResult;
}
else if (const IntersectionType* itv = get<IntersectionType>(ty))
{
2022-03-04 08:36:33 -08:00
for (TypeId id : itv->parts)
{
if (const FunctionType* ftv = get<FunctionType>(id); ftv && checkFunctionType(ftv))
{
2022-03-04 08:36:33 -08:00
return TypeCorrectKind::CorrectFunctionResult;
}
}
}
2022-01-14 08:20:09 -08:00
return checkTypeMatch(ty, expectedType, moduleScope, typeArena, builtinTypes) ? TypeCorrectKind::Correct : TypeCorrectKind::None;
}
enum class PropIndexType
{
Point,
Colon,
Key,
};
static void autocompleteProps(const Module& module, TypeArena* typeArena, NotNull<BuiltinTypes> builtinTypes, TypeId rootTy, TypeId ty,
PropIndexType indexType, const std::vector<AstNode*>& nodes, AutocompleteEntryMap& result, std::unordered_set<TypeId>& seen,
std::optional<const ClassType*> containingClass = std::nullopt)
{
rootTy = follow(rootTy);
ty = follow(ty);
if (seen.count(ty))
return;
seen.insert(ty);
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;
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
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
{
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;
};
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
if (const IntersectionType* itv = get<IntersectionType>(type))
2022-03-17 17:46:04 -07:00
{
for (auto subType : itv->parts)
{
if (const FunctionType* ftv = get<FunctionType>(Luau::follow(subType)))
2022-03-17 17:46:04 -07:00
{
if (isCompatibleCall(ftv))
return false;
}
}
}
return calledWithSelf;
};
auto fillProps = [&](const ClassType::Props& props) {
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)
{
Luau::TypeId type;
if (FFlag::DebugLuauReadWriteProperties)
{
if (auto ty = prop.readType())
type = follow(*ty);
else
continue;
}
else
type = follow(prop.type());
TypeCorrectKind typeCorrect = indexType == PropIndexType::Key
? TypeCorrectKind::Correct
: checkTypeCorrectKind(module, typeArena, builtinTypes, nodes.back(), {{}, {}}, type);
ParenthesesRecommendation parens =
indexType == PropIndexType::Key ? ParenthesesRecommendation::None : getParenRecommendation(type, nodes, typeCorrect);
result[name] = AutocompleteEntry{AutocompleteEntryKind::Property, type, prop.deprecated, isWrongIndexer(type), typeCorrect,
containingClass, &prop, prop.documentationSymbol, {}, parens, {}, indexType == PropIndexType::Colon};
}
}
};
auto fillMetatableProps = [&](const TableType* mtable) {
2022-03-17 17:46:04 -07:00
auto indexIt = mtable->props.find("__index");
if (indexIt != mtable->props.end())
{
TypeId followed = follow(indexIt->second.type());
if (get<TableType>(followed) || get<MetatableType>(followed))
2022-03-17 17:46:04 -07:00
{
autocompleteProps(module, typeArena, builtinTypes, rootTy, followed, indexType, nodes, result, seen);
2022-03-17 17:46:04 -07: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)
autocompleteProps(module, typeArena, builtinTypes, rootTy, *indexFunctionResult, indexType, nodes, result, seen);
2022-03-17 17:46:04 -07:00
}
}
};
if (auto cls = get<ClassType>(ty))
{
containingClass = containingClass.value_or(cls);
fillProps(cls->props);
if (cls->parent)
autocompleteProps(module, typeArena, builtinTypes, rootTy, *cls->parent, indexType, nodes, result, seen, containingClass);
}
else if (auto tbl = get<TableType>(ty))
fillProps(tbl->props);
else if (auto mt = get<MetatableType>(ty))
{
autocompleteProps(module, typeArena, builtinTypes, rootTy, mt->table, indexType, nodes, result, seen);
Sync to upstream/release/566 (#853) * Fixed incorrect lexeme generated for string parts in the middle of an interpolated string (Fixes https://github.com/Roblox/luau/issues/744) * DeprecatedApi lint can report some issues without type inference information * Fixed performance of autocomplete requests when suggestions have large intersection types (Solves https://github.com/Roblox/luau/discussions/847) * Marked `table.getn`/`foreach`/`foreachi` as deprecated ([RFC: Deprecate table.getn/foreach/foreachi](https://github.com/Roblox/luau/blob/master/rfcs/deprecate-table-getn-foreach.md)) * With -O2 optimization level, we now optimize builtin calls based on known argument/return count. Note that this change can be observable if `getfenv/setfenv` is used to substitute a builtin, especially if arity is different. Fastcall heavy tests show a 1-2% improvement. * Luau can now be built with clang-cl (Fixes https://github.com/Roblox/luau/issues/736) We also made many improvements to our experimental components. For our new type solver: * Overhauled data flow analysis system, fixed issues with 'repeat' loops, global variables and type annotations * Type refinements now work on generic table indexing with a string literal * Type refinements will properly track potentially 'nil' values (like t[x] for a missing key) and their further refinements * Internal top table type is now isomorphic to `{}` which fixes issues when `typeof(v) == 'table'` type refinement is handled * References to non-existent types in type annotations no longer resolve to 'error' type like in old solver * Improved handling of class unions in property access expressions * Fixed default type packs * Unsealed tables can now have metatables * Restored expected types for function arguments And for native code generation: * Added min and max IR instructions mapping to vminsd/vmaxsd on x64 * We now speculatively extract direct execution fast-paths based on expected types of expressions which provides better optimization opportunities inside a single basic block * Translated existing math fastcalls to IR form to improve tag guard removal and constant propagation
2023-03-03 22:21:14 +02:00
if (auto mtable = get<TableType>(follow(mt->metatable)))
fillMetatableProps(mtable);
}
else if (auto i = get<IntersectionType>(ty))
{
// Complete all properties in every variant
for (TypeId ty : i->parts)
{
AutocompleteEntryMap inner;
std::unordered_set<TypeId> innerSeen = seen;
autocompleteProps(module, typeArena, builtinTypes, rootTy, ty, indexType, nodes, inner, innerSeen);
for (auto& pair : inner)
result.insert(pair);
}
}
else if (auto u = get<UnionType>(ty))
{
// Complete all properties common to all variants
auto iter = begin(u);
auto endIter = end(u);
while (iter != endIter)
{
if (isNil(*iter))
++iter;
else
break;
}
if (iter == endIter)
return;
autocompleteProps(module, typeArena, builtinTypes, rootTy, *iter, indexType, nodes, result, seen);
++iter;
while (iter != endIter)
{
AutocompleteEntryMap inner;
2022-03-17 17:46:04 -07:00
std::unordered_set<TypeId> innerSeen;
if (isNil(*iter))
{
++iter;
continue;
}
autocompleteProps(module, typeArena, builtinTypes, rootTy, *iter, indexType, nodes, inner, innerSeen);
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;
}
}
else if (auto pt = get<PrimitiveType>(ty))
2022-03-17 17:46:04 -07:00
{
if (pt->metatable)
{
if (auto mtable = get<TableType>(*pt->metatable))
2022-03-17 17:46:04 -07:00
fillMetatableProps(mtable);
}
}
else if (get<StringSingleton>(get<SingletonType>(ty)))
2022-03-17 17:46:04 -07:00
{
autocompleteProps(module, typeArena, builtinTypes, rootTy, builtinTypes->stringType, indexType, nodes, result, seen);
2022-03-17 17:46:04 -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};
}
}
static void autocompleteProps(const Module& module, TypeArena* typeArena, NotNull<BuiltinTypes> builtinTypes, TypeId ty, PropIndexType indexType,
const std::vector<AstNode*>& nodes, AutocompleteEntryMap& result)
{
std::unordered_set<TypeId> seen;
autocompleteProps(module, typeArena, builtinTypes, ty, ty, indexType, nodes, result, seen);
}
AutocompleteEntryMap autocompleteProps(const Module& module, TypeArena* typeArena, NotNull<BuiltinTypes> builtinTypes, TypeId ty,
PropIndexType indexType, const std::vector<AstNode*>& nodes)
{
AutocompleteEntryMap result;
autocompleteProps(module, typeArena, builtinTypes, ty, indexType, nodes, result);
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;
}
static void autocompleteStringSingleton(TypeId ty, bool addQuotes, AstNode* node, Position position, AutocompleteEntryMap& result)
2022-04-07 14:29:01 -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);
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};
}
else if (auto uty = get<UnionType>(ty))
2022-04-07 14:29:01 -07:00
{
for (auto el : uty)
{
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};
}
}
};
static bool canSuggestInferredType(ScopePtr scope, TypeId ty)
{
ty = follow(ty);
// No point in suggesting 'any', invalid to suggest others
if (get<AnyType>(ty) || get<ErrorType>(ty) || get<GenericType>(ty) || get<FreeType>(ty))
return false;
// No syntax for unnamed tables with a metatable
if (get<MetatableType>(ty))
return false;
if (const TableType* ttv = get<TableType>(ty))
{
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>())
{
const FunctionType* ftv = get<FunctionType>(ty);
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))
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 {};
}
template<typename T>
Sync to upstream/release/588 (#992) Type checker/autocomplete: * `Luau::autocomplete` no longer performs typechecking internally, make sure to run `Frontend::check` before performing autocomplete requests * Autocomplete string suggestions without "" are now only suggested inside the "" * Autocomplete suggestions now include `function (anonymous autofilled)` key with a full suggestion for the function expression (with arguments included) stored in `AutocompleteEntry::insertText` * `AutocompleteEntry::indexedWithSelf` is provided for function call suggestions made with `:` * Cyclic modules now see each other type exports as `any` to prevent memory use-after-free (similar to module return type) Runtime: * Updated inline/loop unroll cost model to better handle assignments (Fixes https://github.com/Roblox/luau/issues/978) * `math.noise` speed was improved by ~30% * `table.concat` speed was improved by ~5-7% * `tonumber` and `tostring` now have fastcall paths that execute ~1.5x and ~2.5x faster respectively (fixes #777) * Fixed crash in `luaL_typename` when index refers to a non-existing value * Fixed potential out of memory scenario when using `string.sub` or `string.char` in a loop * Fixed behavior of some fastcall builtins when called without arguments under -O2 to match original functions * Support for native code execution in VM is now enabled by default (note: native code still has to be generated explicitly) * `Codegen::compile` now accepts `CodeGen_OnlyNativeModules` flag. When set, only modules that have a `--!native` hot-comment at the top will be compiled to native code In our new typechecker: * Generic type packs are no longer considered to be variadic during unification * Timeout and cancellation now works in new solver * Fixed false positive errors around 'table' and 'function' type refinements * Table literals now use covariant unification rules. This is sound since literal has no type specified and has no aliases * Fixed issues with blocked types escaping the constraint solver * Fixed more places where error messages that should've been suppressed were still reported * Fixed errors when iterating over a top table type In our native code generation (jit): * 'DebugLuauAbortingChecks' flag is now supported on A64 * LOP_NEWCLOSURE has been translated to IR
2023-07-28 08:13:53 -07:00
static std::optional<std::string> tryToStringDetailed(const ScopePtr& scope, T ty, bool functionTypeArguments)
{
ToStringOptions opts;
opts.useLineBreaks = false;
opts.hideTableKind = true;
Sync to upstream/release/588 (#992) Type checker/autocomplete: * `Luau::autocomplete` no longer performs typechecking internally, make sure to run `Frontend::check` before performing autocomplete requests * Autocomplete string suggestions without "" are now only suggested inside the "" * Autocomplete suggestions now include `function (anonymous autofilled)` key with a full suggestion for the function expression (with arguments included) stored in `AutocompleteEntry::insertText` * `AutocompleteEntry::indexedWithSelf` is provided for function call suggestions made with `:` * Cyclic modules now see each other type exports as `any` to prevent memory use-after-free (similar to module return type) Runtime: * Updated inline/loop unroll cost model to better handle assignments (Fixes https://github.com/Roblox/luau/issues/978) * `math.noise` speed was improved by ~30% * `table.concat` speed was improved by ~5-7% * `tonumber` and `tostring` now have fastcall paths that execute ~1.5x and ~2.5x faster respectively (fixes #777) * Fixed crash in `luaL_typename` when index refers to a non-existing value * Fixed potential out of memory scenario when using `string.sub` or `string.char` in a loop * Fixed behavior of some fastcall builtins when called without arguments under -O2 to match original functions * Support for native code execution in VM is now enabled by default (note: native code still has to be generated explicitly) * `Codegen::compile` now accepts `CodeGen_OnlyNativeModules` flag. When set, only modules that have a `--!native` hot-comment at the top will be compiled to native code In our new typechecker: * Generic type packs are no longer considered to be variadic during unification * Timeout and cancellation now works in new solver * Fixed false positive errors around 'table' and 'function' type refinements * Table literals now use covariant unification rules. This is sound since literal has no type specified and has no aliases * Fixed issues with blocked types escaping the constraint solver * Fixed more places where error messages that should've been suppressed were still reported * Fixed errors when iterating over a top table type In our native code generation (jit): * 'DebugLuauAbortingChecks' flag is now supported on A64 * LOP_NEWCLOSURE has been translated to IR
2023-07-28 08:13:53 -07:00
opts.functionTypeArguments = functionTypeArguments;
opts.scope = scope;
ToStringResult name = toStringDetailed(ty, opts);
if (name.error || name.invalid || name.cycle || name.truncated)
return std::nullopt;
return name.name;
}
Sync to upstream/release/588 (#992) Type checker/autocomplete: * `Luau::autocomplete` no longer performs typechecking internally, make sure to run `Frontend::check` before performing autocomplete requests * Autocomplete string suggestions without "" are now only suggested inside the "" * Autocomplete suggestions now include `function (anonymous autofilled)` key with a full suggestion for the function expression (with arguments included) stored in `AutocompleteEntry::insertText` * `AutocompleteEntry::indexedWithSelf` is provided for function call suggestions made with `:` * Cyclic modules now see each other type exports as `any` to prevent memory use-after-free (similar to module return type) Runtime: * Updated inline/loop unroll cost model to better handle assignments (Fixes https://github.com/Roblox/luau/issues/978) * `math.noise` speed was improved by ~30% * `table.concat` speed was improved by ~5-7% * `tonumber` and `tostring` now have fastcall paths that execute ~1.5x and ~2.5x faster respectively (fixes #777) * Fixed crash in `luaL_typename` when index refers to a non-existing value * Fixed potential out of memory scenario when using `string.sub` or `string.char` in a loop * Fixed behavior of some fastcall builtins when called without arguments under -O2 to match original functions * Support for native code execution in VM is now enabled by default (note: native code still has to be generated explicitly) * `Codegen::compile` now accepts `CodeGen_OnlyNativeModules` flag. When set, only modules that have a `--!native` hot-comment at the top will be compiled to native code In our new typechecker: * Generic type packs are no longer considered to be variadic during unification * Timeout and cancellation now works in new solver * Fixed false positive errors around 'table' and 'function' type refinements * Table literals now use covariant unification rules. This is sound since literal has no type specified and has no aliases * Fixed issues with blocked types escaping the constraint solver * Fixed more places where error messages that should've been suppressed were still reported * Fixed errors when iterating over a top table type In our native code generation (jit): * 'DebugLuauAbortingChecks' flag is now supported on A64 * LOP_NEWCLOSURE has been translated to IR
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;
return tryToStringDetailed(scope, ty, functionTypeArguments);
Sync to upstream/release/588 (#992) Type checker/autocomplete: * `Luau::autocomplete` no longer performs typechecking internally, make sure to run `Frontend::check` before performing autocomplete requests * Autocomplete string suggestions without "" are now only suggested inside the "" * Autocomplete suggestions now include `function (anonymous autofilled)` key with a full suggestion for the function expression (with arguments included) stored in `AutocompleteEntry::insertText` * `AutocompleteEntry::indexedWithSelf` is provided for function call suggestions made with `:` * Cyclic modules now see each other type exports as `any` to prevent memory use-after-free (similar to module return type) Runtime: * Updated inline/loop unroll cost model to better handle assignments (Fixes https://github.com/Roblox/luau/issues/978) * `math.noise` speed was improved by ~30% * `table.concat` speed was improved by ~5-7% * `tonumber` and `tostring` now have fastcall paths that execute ~1.5x and ~2.5x faster respectively (fixes #777) * Fixed crash in `luaL_typename` when index refers to a non-existing value * Fixed potential out of memory scenario when using `string.sub` or `string.char` in a loop * Fixed behavior of some fastcall builtins when called without arguments under -O2 to match original functions * Support for native code execution in VM is now enabled by default (note: native code still has to be generated explicitly) * `Codegen::compile` now accepts `CodeGen_OnlyNativeModules` flag. When set, only modules that have a `--!native` hot-comment at the top will be compiled to native code In our new typechecker: * Generic type packs are no longer considered to be variadic during unification * Timeout and cancellation now works in new solver * Fixed false positive errors around 'table' and 'function' type refinements * Table literals now use covariant unification rules. This is sound since literal has no type specified and has no aliases * Fixed issues with blocked types escaping the constraint solver * Fixed more places where error messages that should've been suppressed were still reported * Fixed errors when iterating over a top table type In our native code generation (jit): * 'DebugLuauAbortingChecks' flag is now supported on A64 * LOP_NEWCLOSURE has been translated to IR
2023-07-28 08:13:53 -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>
std::optional<const T*> returnFirstNonnullOptionOfType(const UnionType* utv)
{
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;
}
static std::optional<bool> functionIsExpectedAt(const Module& module, AstNode* node, Position position)
{
auto typeAtPosition = findExpectedTypeAt(module, node, position);
if (!typeAtPosition)
return std::nullopt;
TypeId expectedType = follow(*typeAtPosition);
if (get<FunctionType>(expectedType))
return true;
if (const IntersectionType* itv = get<IntersectionType>(expectedType))
{
return std::all_of(begin(itv->parts), end(itv->parts), [](auto&& ty) {
return get<FunctionType>(Luau::follow(ty)) != nullptr;
});
}
if (const UnionType* utv = get<UnionType>(expectedType))
return returnFirstNonnullOptionOfType<FunctionType>(utv).has_value();
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?
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>())
{
if (auto it = module.astTypes.find(exprCall->func))
{
if (const FunctionType* ftv = get<FunctionType>(follow(*it)))
{
2022-06-16 18:05:14 -07:00
if (auto ty = tryGetTypePackTypeAt(ftv->retTypes, tailPos))
inferredType = *ty;
}
}
}
else
{
if (tailPos != 0)
break;
if (auto it = module.astTypes.find(expr))
inferredType = *it;
}
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
auto tryGetExpectedFunctionType = [](const Module& module, AstExpr* expr) -> const FunctionType* {
auto it = module.astExpectedTypes.find(expr);
if (!it)
return nullptr;
TypeId ty = follow(*it);
if (const FunctionType* ftv = get<FunctionType>(ty))
return ftv;
// Handle optional function type
if (const UnionType* utv = get<UnionType>(ty))
{
return returnFirstNonnullOptionOfType<FunctionType>(utv).value_or(nullptr);
}
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))
{
if (const FunctionType* ftv = tryGetExpectedFunctionType(module, node))
{
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))
{
if (const FunctionType* ftv = tryGetExpectedFunctionType(module, node))
{
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++)
{
2022-02-17 17:18:01 -08:00
AstType* ret = node->returnAnnotation->types.data[i];
if (ret->location.containsClosed(position))
{
if (const FunctionType* ftv = tryGetExpectedFunctionType(module, node))
{
2022-06-16 18:05:14 -07:00
if (auto ty = tryGetTypePackTypeAt(ftv->retTypes, i))
tryAddTypeCorrectSuggestion(result, startScope, topType, *ty, position);
}
// TODO: with additional type information, we could suggest inferred return type here
break;
}
}
2022-02-17 17:18:01 -08:00
if (AstTypePack* retTp = node->returnAnnotation->tailType)
{
if (auto variadic = retTp->as<AstTypePackVariadic>())
{
if (variadic->location.containsClosed(position))
{
if (const FunctionType* ftv = tryGetExpectedFunctionType(module, node))
{
2022-06-16 18:05:14 -07:00
if (auto ty = tryGetTypePackTypeAt(ftv->retTypes, ~0u))
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;
if (!grandParent)
return nullptr;
if (T* t = parent->as<T>(); t && grandParent->is<AstStatBlock>())
return t;
if (!greatGrandParent)
return nullptr;
if (T* t = greatGrandParent->as<T>(); t && grandParent->is<AstStatBlock>() && parent->is<AstStatError>() && isIdentifier(node))
return t;
return nullptr;
}
static bool isBindingLegalAtCurrentPosition(const Symbol& symbol, const Binding& binding, Position pos)
{
if (symbol.local)
return binding.location.end < pos;
// 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);
}
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)
{
if (!isBindingLegalAtCurrentPosition(name, binding, position))
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});
Sync to upstream/release/610 (#1154) # What's changed? * Check interrupt handler inside the pattern match engine to eliminate potential for programs to hang during string library function execution. * Allow iteration over table properties to pass the old type solver. ### Native Code Generation * Use in-place memory operands for math library operations on x64. * Replace opaque bools with separate enum classes in IrDump to improve code maintainability. * Translate operations on inferred vectors to IR. * Enable support for debugging native-compiled functions in Roblox Studio. ### New Type Solver * Rework type inference for boolean and string literals to introduce bounded free types (bounded below by the singleton type, and above by the primitive type) and reworked primitive type constraint to decide which is the appropriate type for the literal. * Introduce `FunctionCheckConstraint` to handle bidirectional typechecking for function calls, pushing the expected parameter types from the function onto the arguments. * Introduce `union` and `intersect` type families to compute deferred simplified unions and intersections to be employed by the constraint generation logic in the new solver. * Implement support for expanding the domain of local types in `Unifier2`. * Rework type inference for iteration variables bound by for in loops to use local types. * Change constraint blocking logic to use a set to prevent accidental re-blocking. * Add logic to detect missing return statements in functions. ### Internal Contributors Co-authored-by: Aaron Weiss <aaronweiss@roblox.com> Co-authored-by: Alexander McCord <amccord@roblox.com> Co-authored-by: Andy Friesen <afriesen@roblox.com> Co-authored-by: Aviral Goel <agoel@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com> --------- Co-authored-by: Alexander McCord <amccord@roblox.com> Co-authored-by: Andy Friesen <afriesen@roblox.com> Co-authored-by: Vighnesh <vvijay@roblox.com> Co-authored-by: Aviral Goel <agoel@roblox.com> Co-authored-by: David Cope <dcope@roblox.com> Co-authored-by: Lily Brown <lbrown@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
2024-01-26 19:20:56 -08:00
for (auto it = ancestry.rbegin(); it != ancestry.rend(); ++it)
{
Sync to upstream/release/610 (#1154) # What's changed? * Check interrupt handler inside the pattern match engine to eliminate potential for programs to hang during string library function execution. * Allow iteration over table properties to pass the old type solver. ### Native Code Generation * Use in-place memory operands for math library operations on x64. * Replace opaque bools with separate enum classes in IrDump to improve code maintainability. * Translate operations on inferred vectors to IR. * Enable support for debugging native-compiled functions in Roblox Studio. ### New Type Solver * Rework type inference for boolean and string literals to introduce bounded free types (bounded below by the singleton type, and above by the primitive type) and reworked primitive type constraint to decide which is the appropriate type for the literal. * Introduce `FunctionCheckConstraint` to handle bidirectional typechecking for function calls, pushing the expected parameter types from the function onto the arguments. * Introduce `union` and `intersect` type families to compute deferred simplified unions and intersections to be employed by the constraint generation logic in the new solver. * Implement support for expanding the domain of local types in `Unifier2`. * Rework type inference for iteration variables bound by for in loops to use local types. * Change constraint blocking logic to use a set to prevent accidental re-blocking. * Add logic to detect missing return statements in functions. ### Internal Contributors Co-authored-by: Aaron Weiss <aaronweiss@roblox.com> Co-authored-by: Alexander McCord <amccord@roblox.com> Co-authored-by: Andy Friesen <afriesen@roblox.com> Co-authored-by: Aviral Goel <agoel@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com> --------- Co-authored-by: Alexander McCord <amccord@roblox.com> Co-authored-by: Andy Friesen <afriesen@roblox.com> Co-authored-by: Vighnesh <vvijay@roblox.com> Co-authored-by: Aviral Goel <agoel@roblox.com> Co-authored-by: David Cope <dcope@roblox.com> Co-authored-by: Lily Brown <lbrown@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
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>())
{
Sync to upstream/release/610 (#1154) # What's changed? * Check interrupt handler inside the pattern match engine to eliminate potential for programs to hang during string library function execution. * Allow iteration over table properties to pass the old type solver. ### Native Code Generation * Use in-place memory operands for math library operations on x64. * Replace opaque bools with separate enum classes in IrDump to improve code maintainability. * Translate operations on inferred vectors to IR. * Enable support for debugging native-compiled functions in Roblox Studio. ### New Type Solver * Rework type inference for boolean and string literals to introduce bounded free types (bounded below by the singleton type, and above by the primitive type) and reworked primitive type constraint to decide which is the appropriate type for the literal. * Introduce `FunctionCheckConstraint` to handle bidirectional typechecking for function calls, pushing the expected parameter types from the function onto the arguments. * Introduce `union` and `intersect` type families to compute deferred simplified unions and intersections to be employed by the constraint generation logic in the new solver. * Implement support for expanding the domain of local types in `Unifier2`. * Rework type inference for iteration variables bound by for in loops to use local types. * Change constraint blocking logic to use a set to prevent accidental re-blocking. * Add logic to detect missing return statements in functions. ### Internal Contributors Co-authored-by: Aaron Weiss <aaronweiss@roblox.com> Co-authored-by: Alexander McCord <amccord@roblox.com> Co-authored-by: Andy Friesen <afriesen@roblox.com> Co-authored-by: Aviral Goel <agoel@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com> --------- Co-authored-by: Alexander McCord <amccord@roblox.com> Co-authored-by: Andy Friesen <afriesen@roblox.com> Co-authored-by: Vighnesh <vvijay@roblox.com> Co-authored-by: Aviral Goel <agoel@roblox.com> Co-authored-by: David Cope <dcope@roblox.com> Co-authored-by: Lily Brown <lbrown@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
2024-01-26 19:20:56 -08:00
bool hasEnd = statIf->thenbody->hasEnd;
if (statIf->elsebody)
{
Sync to upstream/release/610 (#1154) # What's changed? * Check interrupt handler inside the pattern match engine to eliminate potential for programs to hang during string library function execution. * Allow iteration over table properties to pass the old type solver. ### Native Code Generation * Use in-place memory operands for math library operations on x64. * Replace opaque bools with separate enum classes in IrDump to improve code maintainability. * Translate operations on inferred vectors to IR. * Enable support for debugging native-compiled functions in Roblox Studio. ### New Type Solver * Rework type inference for boolean and string literals to introduce bounded free types (bounded below by the singleton type, and above by the primitive type) and reworked primitive type constraint to decide which is the appropriate type for the literal. * Introduce `FunctionCheckConstraint` to handle bidirectional typechecking for function calls, pushing the expected parameter types from the function onto the arguments. * Introduce `union` and `intersect` type families to compute deferred simplified unions and intersections to be employed by the constraint generation logic in the new solver. * Implement support for expanding the domain of local types in `Unifier2`. * Rework type inference for iteration variables bound by for in loops to use local types. * Change constraint blocking logic to use a set to prevent accidental re-blocking. * Add logic to detect missing return statements in functions. ### Internal Contributors Co-authored-by: Aaron Weiss <aaronweiss@roblox.com> Co-authored-by: Alexander McCord <amccord@roblox.com> Co-authored-by: Andy Friesen <afriesen@roblox.com> Co-authored-by: Aviral Goel <agoel@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com> --------- Co-authored-by: Alexander McCord <amccord@roblox.com> Co-authored-by: Andy Friesen <afriesen@roblox.com> Co-authored-by: Vighnesh <vvijay@roblox.com> Co-authored-by: Aviral Goel <agoel@roblox.com> Co-authored-by: David Cope <dcope@roblox.com> Co-authored-by: Lily Brown <lbrown@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
2024-01-26 19:20:56 -08:00
if (AstStatBlock* elseBlock = statIf->elsebody->as<AstStatBlock>())
hasEnd = elseBlock->hasEnd;
}
Sync to upstream/release/610 (#1154) # What's changed? * Check interrupt handler inside the pattern match engine to eliminate potential for programs to hang during string library function execution. * Allow iteration over table properties to pass the old type solver. ### Native Code Generation * Use in-place memory operands for math library operations on x64. * Replace opaque bools with separate enum classes in IrDump to improve code maintainability. * Translate operations on inferred vectors to IR. * Enable support for debugging native-compiled functions in Roblox Studio. ### New Type Solver * Rework type inference for boolean and string literals to introduce bounded free types (bounded below by the singleton type, and above by the primitive type) and reworked primitive type constraint to decide which is the appropriate type for the literal. * Introduce `FunctionCheckConstraint` to handle bidirectional typechecking for function calls, pushing the expected parameter types from the function onto the arguments. * Introduce `union` and `intersect` type families to compute deferred simplified unions and intersections to be employed by the constraint generation logic in the new solver. * Implement support for expanding the domain of local types in `Unifier2`. * Rework type inference for iteration variables bound by for in loops to use local types. * Change constraint blocking logic to use a set to prevent accidental re-blocking. * Add logic to detect missing return statements in functions. ### Internal Contributors Co-authored-by: Aaron Weiss <aaronweiss@roblox.com> Co-authored-by: Alexander McCord <amccord@roblox.com> Co-authored-by: Andy Friesen <afriesen@roblox.com> Co-authored-by: Aviral Goel <agoel@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com> --------- Co-authored-by: Alexander McCord <amccord@roblox.com> Co-authored-by: Andy Friesen <afriesen@roblox.com> Co-authored-by: Vighnesh <vvijay@roblox.com> Co-authored-by: Aviral Goel <agoel@roblox.com> Co-authored-by: David Cope <dcope@roblox.com> Co-authored-by: Lily Brown <lbrown@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
2024-01-26 19:20:56 -08:00
if (!hasEnd)
result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword});
}
Sync to upstream/release/610 (#1154) # What's changed? * Check interrupt handler inside the pattern match engine to eliminate potential for programs to hang during string library function execution. * Allow iteration over table properties to pass the old type solver. ### Native Code Generation * Use in-place memory operands for math library operations on x64. * Replace opaque bools with separate enum classes in IrDump to improve code maintainability. * Translate operations on inferred vectors to IR. * Enable support for debugging native-compiled functions in Roblox Studio. ### New Type Solver * Rework type inference for boolean and string literals to introduce bounded free types (bounded below by the singleton type, and above by the primitive type) and reworked primitive type constraint to decide which is the appropriate type for the literal. * Introduce `FunctionCheckConstraint` to handle bidirectional typechecking for function calls, pushing the expected parameter types from the function onto the arguments. * Introduce `union` and `intersect` type families to compute deferred simplified unions and intersections to be employed by the constraint generation logic in the new solver. * Implement support for expanding the domain of local types in `Unifier2`. * Rework type inference for iteration variables bound by for in loops to use local types. * Change constraint blocking logic to use a set to prevent accidental re-blocking. * Add logic to detect missing return statements in functions. ### Internal Contributors Co-authored-by: Aaron Weiss <aaronweiss@roblox.com> Co-authored-by: Alexander McCord <amccord@roblox.com> Co-authored-by: Andy Friesen <afriesen@roblox.com> Co-authored-by: Aviral Goel <agoel@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com> --------- Co-authored-by: Alexander McCord <amccord@roblox.com> Co-authored-by: Andy Friesen <afriesen@roblox.com> Co-authored-by: Vighnesh <vvijay@roblox.com> Co-authored-by: Aviral Goel <agoel@roblox.com> Co-authored-by: David Cope <dcope@roblox.com> Co-authored-by: Lily Brown <lbrown@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
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});
}
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)))
{
result.emplace("else", AutocompleteEntry{AutocompleteEntryKind::Keyword});
result.emplace("elseif", AutocompleteEntry{AutocompleteEntryKind::Keyword});
}
}
Sync to upstream/release/610 (#1154) # What's changed? * Check interrupt handler inside the pattern match engine to eliminate potential for programs to hang during string library function execution. * Allow iteration over table properties to pass the old type solver. ### Native Code Generation * Use in-place memory operands for math library operations on x64. * Replace opaque bools with separate enum classes in IrDump to improve code maintainability. * Translate operations on inferred vectors to IR. * Enable support for debugging native-compiled functions in Roblox Studio. ### New Type Solver * Rework type inference for boolean and string literals to introduce bounded free types (bounded below by the singleton type, and above by the primitive type) and reworked primitive type constraint to decide which is the appropriate type for the literal. * Introduce `FunctionCheckConstraint` to handle bidirectional typechecking for function calls, pushing the expected parameter types from the function onto the arguments. * Introduce `union` and `intersect` type families to compute deferred simplified unions and intersections to be employed by the constraint generation logic in the new solver. * Implement support for expanding the domain of local types in `Unifier2`. * Rework type inference for iteration variables bound by for in loops to use local types. * Change constraint blocking logic to use a set to prevent accidental re-blocking. * Add logic to detect missing return statements in functions. ### Internal Contributors Co-authored-by: Aaron Weiss <aaronweiss@roblox.com> Co-authored-by: Alexander McCord <amccord@roblox.com> Co-authored-by: Andy Friesen <afriesen@roblox.com> Co-authored-by: Aviral Goel <agoel@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com> --------- Co-authored-by: Alexander McCord <amccord@roblox.com> Co-authored-by: Andy Friesen <afriesen@roblox.com> Co-authored-by: Vighnesh <vvijay@roblox.com> Co-authored-by: Aviral Goel <agoel@roblox.com> Co-authored-by: David Cope <dcope@roblox.com> Co-authored-by: Lily Brown <lbrown@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
2024-01-26 19:20:56 -08:00
if (AstStatRepeat* statRepeat = parent->as<AstStatRepeat>(); statRepeat && !statRepeat->body->hasEnd)
result.emplace("until", AutocompleteEntry{AutocompleteEntryKind::Keyword});
}
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});
}
}
Sync to upstream/release/610 (#1154) # What's changed? * Check interrupt handler inside the pattern match engine to eliminate potential for programs to hang during string library function execution. * Allow iteration over table properties to pass the old type solver. ### Native Code Generation * Use in-place memory operands for math library operations on x64. * Replace opaque bools with separate enum classes in IrDump to improve code maintainability. * Translate operations on inferred vectors to IR. * Enable support for debugging native-compiled functions in Roblox Studio. ### New Type Solver * Rework type inference for boolean and string literals to introduce bounded free types (bounded below by the singleton type, and above by the primitive type) and reworked primitive type constraint to decide which is the appropriate type for the literal. * Introduce `FunctionCheckConstraint` to handle bidirectional typechecking for function calls, pushing the expected parameter types from the function onto the arguments. * Introduce `union` and `intersect` type families to compute deferred simplified unions and intersections to be employed by the constraint generation logic in the new solver. * Implement support for expanding the domain of local types in `Unifier2`. * Rework type inference for iteration variables bound by for in loops to use local types. * Change constraint blocking logic to use a set to prevent accidental re-blocking. * Add logic to detect missing return statements in functions. ### Internal Contributors Co-authored-by: Aaron Weiss <aaronweiss@roblox.com> Co-authored-by: Alexander McCord <amccord@roblox.com> Co-authored-by: Andy Friesen <afriesen@roblox.com> Co-authored-by: Aviral Goel <agoel@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com> --------- Co-authored-by: Alexander McCord <amccord@roblox.com> Co-authored-by: Andy Friesen <afriesen@roblox.com> Co-authored-by: Vighnesh <vvijay@roblox.com> Co-authored-by: Aviral Goel <agoel@roblox.com> Co-authored-by: David Cope <dcope@roblox.com> Co-authored-by: Lily Brown <lbrown@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
2024-01-26 19:20:56 -08:00
if (AstStatRepeat* statRepeat = extractStat<AstStatRepeat>(ancestry); statRepeat && !statRepeat->body->hasEnd)
result.emplace("until", AutocompleteEntry{AutocompleteEntryKind::Keyword});
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`)
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;
}
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;
}
}
static AutocompleteContext autocompleteExpression(const SourceModule& sourceModule, const Module& module, NotNull<BuiltinTypes> builtinTypes,
TypeArena* typeArena, const std::vector<AstNode*>& ancestry, Position position, AutocompleteEntryMap& result)
{
LUAU_ASSERT(!ancestry.empty());
AstNode* node = ancestry.rbegin()[0];
if (node->is<AstExprIndexName>())
{
if (auto it = module.astTypes.find(node->asExpr()))
autocompleteProps(module, typeArena, builtinTypes, *it, PropIndexType::Point, ancestry, result);
}
2022-01-14 08:20:09 -08:00
else if (autocompleteIfElseExpression(node, ancestry, position, result))
return AutocompleteContext::Keyword;
else if (node->is<AstExprFunction>())
return AutocompleteContext::Unknown;
else
{
// This is inefficient. :(
ScopePtr scope = findScopeAtPosition(module, position);
while (scope)
{
for (const auto& [name, binding] : scope->bindings)
{
if (!isBindingLegalAtCurrentPosition(name, binding, position))
continue;
if (isBeingDefined(ancestry, name))
continue;
std::string n = toString(name);
if (!result.count(n))
{
TypeCorrectKind typeCorrect = checkTypeCorrectKind(module, typeArena, builtinTypes, node, position, binding.typeId);
result[n] = {AutocompleteEntryKind::Binding, binding.typeId, binding.deprecated, false, typeCorrect, std::nullopt, std::nullopt,
binding.documentationSymbol, {}, getParenRecommendation(binding.typeId, ancestry, typeCorrect)};
}
}
scope = scope->parent;
}
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};
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))
autocompleteStringSingleton(*ty, true, node, position, result);
}
return AutocompleteContext::Expression;
}
static AutocompleteResult autocompleteExpression(const SourceModule& sourceModule, const Module& module, NotNull<BuiltinTypes> builtinTypes,
TypeArena* typeArena, const std::vector<AstNode*>& ancestry, Position position)
{
AutocompleteEntryMap result;
AutocompleteContext context = autocompleteExpression(sourceModule, module, builtinTypes, typeArena, ancestry, position, result);
return {result, ancestry, context};
}
static std::optional<const ClassType*> getMethodContainingClass(const ModulePtr& module, AstExpr* funcExpr)
{
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;
}
auto parentIt = module->astTypes.find(parentExpr);
if (!parentIt)
{
return std::nullopt;
}
Luau::TypeId parentType = Luau::follow(*parentIt);
if (auto parentClass = Luau::get<ClassType>(parentType))
{
return parentClass;
}
if (auto parentUnion = Luau::get<UnionType>(parentType))
{
return returnFirstNonnullOptionOfType<ClassType>(parentUnion);
}
return std::nullopt;
}
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;
}
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;
}
}
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;
}
if (!nodes.back()->is<AstExprConstantString>() && !isSimpleInterpolatedString(nodes.back()) && !nodes.back()->is<AstExprError>())
{
return std::nullopt;
}
Sync to upstream/release/591 (#1012) * Fix a use-after-free bug in the new type cloning algorithm * Tighten up the type of `coroutine.wrap`. It is now `<A..., R...>(f: (A...) -> R...) -> ((A...) -> R...)` * Break `.luaurc` out into a separate library target `Luau.Config`. This makes it easier for applications to reason about config files without also depending on the type inference engine. * Move typechecking limits into `FrontendOptions`. This allows embedders more finely-grained control over autocomplete's internal time limits. * Fix stability issue with debugger onprotectederror callback allowing break in non-yieldable contexts New solver: * Initial work toward [Local Type Inference](https://github.com/Roblox/luau/blob/0e1082108fd6fb3a32dfdf5f1766ea3fc1391328/rfcs/local-type-inference.md) * Introduce a new subtyping test. This will be much nicer than the old test because it is completely separate both from actual type inference and from error reporting. Native code generation: * Added function to compute iterated dominance frontier * Optimize barriers in SET_UPVALUE when tag is known * Cache lua_State::global in a register on A64 * Optimize constant stores in A64 lowering * Track table array size state to optimize array size checks * Add split tag/value store into a VM register * Check that spills can outlive the block only in specific conditions --------- Co-authored-by: Arseny Kapoulkine <arseny.kapoulkine@gmail.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
2023-08-18 11:15:41 -07:00
if (!nodes.back()->is<AstExprError>())
Sync to upstream/release/588 (#992) Type checker/autocomplete: * `Luau::autocomplete` no longer performs typechecking internally, make sure to run `Frontend::check` before performing autocomplete requests * Autocomplete string suggestions without "" are now only suggested inside the "" * Autocomplete suggestions now include `function (anonymous autofilled)` key with a full suggestion for the function expression (with arguments included) stored in `AutocompleteEntry::insertText` * `AutocompleteEntry::indexedWithSelf` is provided for function call suggestions made with `:` * Cyclic modules now see each other type exports as `any` to prevent memory use-after-free (similar to module return type) Runtime: * Updated inline/loop unroll cost model to better handle assignments (Fixes https://github.com/Roblox/luau/issues/978) * `math.noise` speed was improved by ~30% * `table.concat` speed was improved by ~5-7% * `tonumber` and `tostring` now have fastcall paths that execute ~1.5x and ~2.5x faster respectively (fixes #777) * Fixed crash in `luaL_typename` when index refers to a non-existing value * Fixed potential out of memory scenario when using `string.sub` or `string.char` in a loop * Fixed behavior of some fastcall builtins when called without arguments under -O2 to match original functions * Support for native code execution in VM is now enabled by default (note: native code still has to be generated explicitly) * `Codegen::compile` now accepts `CodeGen_OnlyNativeModules` flag. When set, only modules that have a `--!native` hot-comment at the top will be compiled to native code In our new typechecker: * Generic type packs are no longer considered to be variadic during unification * Timeout and cancellation now works in new solver * Fixed false positive errors around 'table' and 'function' type refinements * Table literals now use covariant unification rules. This is sound since literal has no type specified and has no aliases * Fixed issues with blocked types escaping the constraint solver * Fixed more places where error messages that should've been suppressed were still reported * Fixed errors when iterating over a top table type In our native code generation (jit): * 'DebugLuauAbortingChecks' flag is now supported on A64 * LOP_NEWCLOSURE has been translated to IR
2023-07-28 08:13:53 -07:00
{
if (nodes.back()->location.end == position || nodes.back()->location.begin == position)
{
return std::nullopt;
}
}
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,
// so we encode that here rather than putting a useless member on the FunctionType struct.
if (candidate->args.size > 1 && !candidate->args.data[0]->location.contains(position))
{
return std::nullopt;
}
auto it = module->astTypes.find(candidate->func);
if (!it)
{
return std::nullopt;
}
std::optional<std::string> candidateString = getStringContents(nodes.back());
auto performCallback = [&](const FunctionType* funcType) -> std::optional<AutocompleteEntryMap> {
for (const std::string& tag : funcType->tags)
{
if (std::optional<AutocompleteEntryMap> ret = callback(tag, getMethodContainingClass(module, candidate->func), candidateString))
{
return ret;
}
}
return std::nullopt;
};
auto followedId = Luau::follow(*it);
if (auto functionType = Luau::get<FunctionType>(followedId))
{
return performCallback(functionType);
}
if (auto intersect = Luau::get<IntersectionType>(followedId))
{
for (TypeId part : intersect->parts)
{
if (auto candidateFunctionType = Luau::get<FunctionType>(part))
{
if (std::optional<AutocompleteEntryMap> ret = performCallback(candidateFunctionType))
{
return ret;
}
}
}
}
return std::nullopt;
}
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};
}
Sync to upstream/release/588 (#992) Type checker/autocomplete: * `Luau::autocomplete` no longer performs typechecking internally, make sure to run `Frontend::check` before performing autocomplete requests * Autocomplete string suggestions without "" are now only suggested inside the "" * Autocomplete suggestions now include `function (anonymous autofilled)` key with a full suggestion for the function expression (with arguments included) stored in `AutocompleteEntry::insertText` * `AutocompleteEntry::indexedWithSelf` is provided for function call suggestions made with `:` * Cyclic modules now see each other type exports as `any` to prevent memory use-after-free (similar to module return type) Runtime: * Updated inline/loop unroll cost model to better handle assignments (Fixes https://github.com/Roblox/luau/issues/978) * `math.noise` speed was improved by ~30% * `table.concat` speed was improved by ~5-7% * `tonumber` and `tostring` now have fastcall paths that execute ~1.5x and ~2.5x faster respectively (fixes #777) * Fixed crash in `luaL_typename` when index refers to a non-existing value * Fixed potential out of memory scenario when using `string.sub` or `string.char` in a loop * Fixed behavior of some fastcall builtins when called without arguments under -O2 to match original functions * Support for native code execution in VM is now enabled by default (note: native code still has to be generated explicitly) * `Codegen::compile` now accepts `CodeGen_OnlyNativeModules` flag. When set, only modules that have a `--!native` hot-comment at the top will be compiled to native code In our new typechecker: * Generic type packs are no longer considered to be variadic during unification * Timeout and cancellation now works in new solver * Fixed false positive errors around 'table' and 'function' type refinements * Table literals now use covariant unification rules. This is sound since literal has no type specified and has no aliases * Fixed issues with blocked types escaping the constraint solver * Fixed more places where error messages that should've been suppressed were still reported * Fixed errors when iterating over a top table type In our native code generation (jit): * 'DebugLuauAbortingChecks' flag is now supported on A64 * LOP_NEWCLOSURE has been translated to IR
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))
result += name + ": " + *type;
Sync to upstream/release/588 (#992) Type checker/autocomplete: * `Luau::autocomplete` no longer performs typechecking internally, make sure to run `Frontend::check` before performing autocomplete requests * Autocomplete string suggestions without "" are now only suggested inside the "" * Autocomplete suggestions now include `function (anonymous autofilled)` key with a full suggestion for the function expression (with arguments included) stored in `AutocompleteEntry::insertText` * `AutocompleteEntry::indexedWithSelf` is provided for function call suggestions made with `:` * Cyclic modules now see each other type exports as `any` to prevent memory use-after-free (similar to module return type) Runtime: * Updated inline/loop unroll cost model to better handle assignments (Fixes https://github.com/Roblox/luau/issues/978) * `math.noise` speed was improved by ~30% * `table.concat` speed was improved by ~5-7% * `tonumber` and `tostring` now have fastcall paths that execute ~1.5x and ~2.5x faster respectively (fixes #777) * Fixed crash in `luaL_typename` when index refers to a non-existing value * Fixed potential out of memory scenario when using `string.sub` or `string.char` in a loop * Fixed behavior of some fastcall builtins when called without arguments under -O2 to match original functions * Support for native code execution in VM is now enabled by default (note: native code still has to be generated explicitly) * `Codegen::compile` now accepts `CodeGen_OnlyNativeModules` flag. When set, only modules that have a `--!native` hot-comment at the top will be compiled to native code In our new typechecker: * Generic type packs are no longer considered to be variadic during unification * Timeout and cancellation now works in new solver * Fixed false positive errors around 'table' and 'function' type refinements * Table literals now use covariant unification rules. This is sound since literal has no type specified and has no aliases * Fixed issues with blocked types escaping the constraint solver * Fixed more places where error messages that should've been suppressed were still reported * Fixed errors when iterating over a top table type In our native code generation (jit): * 'DebugLuauAbortingChecks' flag is now supported on A64 * LOP_NEWCLOSURE has been translated to IR
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);
}
Sync to upstream/release/588 (#992) Type checker/autocomplete: * `Luau::autocomplete` no longer performs typechecking internally, make sure to run `Frontend::check` before performing autocomplete requests * Autocomplete string suggestions without "" are now only suggested inside the "" * Autocomplete suggestions now include `function (anonymous autofilled)` key with a full suggestion for the function expression (with arguments included) stored in `AutocompleteEntry::insertText` * `AutocompleteEntry::indexedWithSelf` is provided for function call suggestions made with `:` * Cyclic modules now see each other type exports as `any` to prevent memory use-after-free (similar to module return type) Runtime: * Updated inline/loop unroll cost model to better handle assignments (Fixes https://github.com/Roblox/luau/issues/978) * `math.noise` speed was improved by ~30% * `table.concat` speed was improved by ~5-7% * `tonumber` and `tostring` now have fastcall paths that execute ~1.5x and ~2.5x faster respectively (fixes #777) * Fixed crash in `luaL_typename` when index refers to a non-existing value * Fixed potential out of memory scenario when using `string.sub` or `string.char` in a loop * Fixed behavior of some fastcall builtins when called without arguments under -O2 to match original functions * Support for native code execution in VM is now enabled by default (note: native code still has to be generated explicitly) * `Codegen::compile` now accepts `CodeGen_OnlyNativeModules` flag. When set, only modules that have a `--!native` hot-comment at the top will be compiled to native code In our new typechecker: * Generic type packs are no longer considered to be variadic during unification * Timeout and cancellation now works in new solver * Fixed false positive errors around 'table' and 'function' type refinements * Table literals now use covariant unification rules. This is sound since literal has no type specified and has no aliases * Fixed issues with blocked types escaping the constraint solver * Fixed more places where error messages that should've been suppressed were still reported * Fixed errors when iterating over a top table type In our native code generation (jit): * 'DebugLuauAbortingChecks' flag is now supported on A64 * LOP_NEWCLOSURE has been translated to IR
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;
}
static std::optional<AutocompleteEntry> makeAnonymousAutofilled(
const ModulePtr& module, Position position, const AstNode* node, const std::vector<AstNode*>& ancestry)
Sync to upstream/release/588 (#992) Type checker/autocomplete: * `Luau::autocomplete` no longer performs typechecking internally, make sure to run `Frontend::check` before performing autocomplete requests * Autocomplete string suggestions without "" are now only suggested inside the "" * Autocomplete suggestions now include `function (anonymous autofilled)` key with a full suggestion for the function expression (with arguments included) stored in `AutocompleteEntry::insertText` * `AutocompleteEntry::indexedWithSelf` is provided for function call suggestions made with `:` * Cyclic modules now see each other type exports as `any` to prevent memory use-after-free (similar to module return type) Runtime: * Updated inline/loop unroll cost model to better handle assignments (Fixes https://github.com/Roblox/luau/issues/978) * `math.noise` speed was improved by ~30% * `table.concat` speed was improved by ~5-7% * `tonumber` and `tostring` now have fastcall paths that execute ~1.5x and ~2.5x faster respectively (fixes #777) * Fixed crash in `luaL_typename` when index refers to a non-existing value * Fixed potential out of memory scenario when using `string.sub` or `string.char` in a loop * Fixed behavior of some fastcall builtins when called without arguments under -O2 to match original functions * Support for native code execution in VM is now enabled by default (note: native code still has to be generated explicitly) * `Codegen::compile` now accepts `CodeGen_OnlyNativeModules` flag. When set, only modules that have a `--!native` hot-comment at the top will be compiled to native code In our new typechecker: * Generic type packs are no longer considered to be variadic during unification * Timeout and cancellation now works in new solver * Fixed false positive errors around 'table' and 'function' type refinements * Table literals now use covariant unification rules. This is sound since literal has no type specified and has no aliases * Fixed issues with blocked types escaping the constraint solver * Fixed more places where error messages that should've been suppressed were still reported * Fixed errors when iterating over a top table type In our native code generation (jit): * 'DebugLuauAbortingChecks' flag is now supported on A64 * LOP_NEWCLOSURE has been translated to IR
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];
Sync to upstream/release/588 (#992) Type checker/autocomplete: * `Luau::autocomplete` no longer performs typechecking internally, make sure to run `Frontend::check` before performing autocomplete requests * Autocomplete string suggestions without "" are now only suggested inside the "" * Autocomplete suggestions now include `function (anonymous autofilled)` key with a full suggestion for the function expression (with arguments included) stored in `AutocompleteEntry::insertText` * `AutocompleteEntry::indexedWithSelf` is provided for function call suggestions made with `:` * Cyclic modules now see each other type exports as `any` to prevent memory use-after-free (similar to module return type) Runtime: * Updated inline/loop unroll cost model to better handle assignments (Fixes https://github.com/Roblox/luau/issues/978) * `math.noise` speed was improved by ~30% * `table.concat` speed was improved by ~5-7% * `tonumber` and `tostring` now have fastcall paths that execute ~1.5x and ~2.5x faster respectively (fixes #777) * Fixed crash in `luaL_typename` when index refers to a non-existing value * Fixed potential out of memory scenario when using `string.sub` or `string.char` in a loop * Fixed behavior of some fastcall builtins when called without arguments under -O2 to match original functions * Support for native code execution in VM is now enabled by default (note: native code still has to be generated explicitly) * `Codegen::compile` now accepts `CodeGen_OnlyNativeModules` flag. When set, only modules that have a `--!native` hot-comment at the top will be compiled to native code In our new typechecker: * Generic type packs are no longer considered to be variadic during unification * Timeout and cancellation now works in new solver * Fixed false positive errors around 'table' and 'function' type refinements * Table literals now use covariant unification rules. This is sound since literal has no type specified and has no aliases * Fixed issues with blocked types escaping the constraint solver * Fixed more places where error messages that should've been suppressed were still reported * Fixed errors when iterating over a top table type In our native code generation (jit): * 'DebugLuauAbortingChecks' flag is now supported on A64 * LOP_NEWCLOSURE has been translated to IR
2023-07-28 08:13:53 -07:00
if (!argType)
return std::nullopt;
Sync to upstream/release/588 (#992) Type checker/autocomplete: * `Luau::autocomplete` no longer performs typechecking internally, make sure to run `Frontend::check` before performing autocomplete requests * Autocomplete string suggestions without "" are now only suggested inside the "" * Autocomplete suggestions now include `function (anonymous autofilled)` key with a full suggestion for the function expression (with arguments included) stored in `AutocompleteEntry::insertText` * `AutocompleteEntry::indexedWithSelf` is provided for function call suggestions made with `:` * Cyclic modules now see each other type exports as `any` to prevent memory use-after-free (similar to module return type) Runtime: * Updated inline/loop unroll cost model to better handle assignments (Fixes https://github.com/Roblox/luau/issues/978) * `math.noise` speed was improved by ~30% * `table.concat` speed was improved by ~5-7% * `tonumber` and `tostring` now have fastcall paths that execute ~1.5x and ~2.5x faster respectively (fixes #777) * Fixed crash in `luaL_typename` when index refers to a non-existing value * Fixed potential out of memory scenario when using `string.sub` or `string.char` in a loop * Fixed behavior of some fastcall builtins when called without arguments under -O2 to match original functions * Support for native code execution in VM is now enabled by default (note: native code still has to be generated explicitly) * `Codegen::compile` now accepts `CodeGen_OnlyNativeModules` flag. When set, only modules that have a `--!native` hot-comment at the top will be compiled to native code In our new typechecker: * Generic type packs are no longer considered to be variadic during unification * Timeout and cancellation now works in new solver * Fixed false positive errors around 'table' and 'function' type refinements * Table literals now use covariant unification rules. This is sound since literal has no type specified and has no aliases * Fixed issues with blocked types escaping the constraint solver * Fixed more places where error messages that should've been suppressed were still reported * Fixed errors when iterating over a top table type In our native code generation (jit): * 'DebugLuauAbortingChecks' flag is now supported on A64 * LOP_NEWCLOSURE has been translated to IR
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));
}
static AutocompleteResult autocomplete(const SourceModule& sourceModule, const ModulePtr& module, NotNull<BuiltinTypes> builtinTypes,
TypeArena* typeArena, Scope* globalScope, Position position, StringCompletionCallback callback)
{
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();
AstExprConstantNil dummy{Location{}};
2022-07-07 18:22:39 -07:00
AstNode* parent = ancestry.size() >= 2 ? ancestry.rbegin()[1] : &dummy;
// 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();
2022-07-07 18:22:39 -07:00
node = ancestry.back();
parent = ancestry.size() >= 2 ? ancestry.rbegin()[1] : &dummy;
}
if (auto indexName = node->as<AstExprIndexName>())
{
auto it = module->astTypes.find(indexName->expr);
if (!it)
return {};
TypeId ty = follow(*it);
PropIndexType indexType = indexName->op == ':' ? PropIndexType::Colon : PropIndexType::Point;
return {autocompleteProps(*module, typeArena, builtinTypes, ty, indexType, ancestry), ancestry, AutocompleteContext::Property};
}
else if (auto typeReference = node->as<AstTypeReference>())
{
2022-02-17 17:18:01 -08:00
if (typeReference->prefix)
return {autocompleteModuleTypes(*module, position, typeReference->prefix->value), ancestry, AutocompleteContext::Type};
else
return {autocompleteTypeNames(*module, position, ancestry), ancestry, AutocompleteContext::Type};
}
else if (node->is<AstTypeError>())
{
return {autocompleteTypeNames(*module, position, ancestry), ancestry, AutocompleteContext::Type};
}
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))
return {{{"function", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry, AutocompleteContext::Unknown};
2022-02-17 17:18:01 -08:00
else if (statLocal->equalsSignLocation && position >= statLocal->equalsSignLocation->end)
return autocompleteExpression(sourceModule, *module, builtinTypes, typeArena, ancestry, position);
else
return {};
}
2022-07-07 18:22:39 -07:00
else if (AstStatFor* statFor = extractStat<AstStatFor>(ancestry))
{
if (!statFor->hasDo || position < statFor->doLocation.begin)
{
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);
if (!statFor->from->is<AstExprError>() && !statFor->to->is<AstExprError>() && (!statFor->step || !statFor->step->is<AstExprError>()))
return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry, AutocompleteContext::Keyword};
return {};
}
return {autocompleteStatement(sourceModule, *module, ancestry, position), ancestry, AutocompleteContext::Statement};
}
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))
{
// 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 {};
}
return {{{"in", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry, AutocompleteContext::Keyword};
}
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))
return autocompleteExpression(sourceModule, *module, builtinTypes, typeArena, ancestry, position);
if (position > lastExpr->location.end)
return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry, AutocompleteContext::Keyword};
return {}; // Not sure what this means
}
}
2022-07-07 18:22:39 -07:00
else if (AstStatForIn* statForIn = extractStat<AstStatForIn>(ancestry))
{
// 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)
return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry, AutocompleteContext::Keyword};
return {autocompleteStatement(sourceModule, *module, ancestry, position), ancestry, AutocompleteContext::Statement};
}
else if (AstStatWhile* statWhile = parent->as<AstStatWhile>(); node->is<AstStatBlock>() && statWhile)
{
if (!statWhile->hasDo && !statWhile->condition->is<AstStatError>() && position > statWhile->condition->location.end)
{
return autocompleteWhileLoopKeywords(ancestry);
}
if (!statWhile->hasDo || position < statWhile->doLocation.begin)
return autocompleteExpression(sourceModule, *module, builtinTypes, typeArena, ancestry, position);
if (statWhile->hasDo && position > statWhile->doLocation.end)
return {autocompleteStatement(sourceModule, *module, ancestry, position), ancestry, AutocompleteContext::Statement};
}
else if (AstStatWhile* statWhile = extractStat<AstStatWhile>(ancestry);
(statWhile && (!statWhile->hasDo || statWhile->doLocation.containsClosed(position)) && statWhile->condition &&
!statWhile->condition->location.containsClosed(position)))
{
return autocompleteWhileLoopKeywords(ancestry);
}
2022-02-17 17:18:01 -08:00
else if (AstStatIf* statIf = node->as<AstStatIf>(); statIf && !statIf->elseLocation.has_value())
{
return {{{"else", AutocompleteEntry{AutocompleteEntryKind::Keyword}}, {"elseif", AutocompleteEntry{AutocompleteEntryKind::Keyword}}},
ancestry, AutocompleteContext::Keyword};
}
else if (AstStatIf* statIf = parent->as<AstStatIf>(); statIf && node->is<AstStatBlock>())
{
if (statIf->condition->is<AstExprError>())
return autocompleteExpression(sourceModule, *module, builtinTypes, typeArena, ancestry, position);
2022-02-17 17:18:01 -08:00
else if (!statIf->thenLocation || statIf->thenLocation->containsClosed(position))
return {{{"then", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry, AutocompleteContext::Keyword};
}
Sync to upstream/release/566 (#853) * Fixed incorrect lexeme generated for string parts in the middle of an interpolated string (Fixes https://github.com/Roblox/luau/issues/744) * DeprecatedApi lint can report some issues without type inference information * Fixed performance of autocomplete requests when suggestions have large intersection types (Solves https://github.com/Roblox/luau/discussions/847) * Marked `table.getn`/`foreach`/`foreachi` as deprecated ([RFC: Deprecate table.getn/foreach/foreachi](https://github.com/Roblox/luau/blob/master/rfcs/deprecate-table-getn-foreach.md)) * With -O2 optimization level, we now optimize builtin calls based on known argument/return count. Note that this change can be observable if `getfenv/setfenv` is used to substitute a builtin, especially if arity is different. Fastcall heavy tests show a 1-2% improvement. * Luau can now be built with clang-cl (Fixes https://github.com/Roblox/luau/issues/736) We also made many improvements to our experimental components. For our new type solver: * Overhauled data flow analysis system, fixed issues with 'repeat' loops, global variables and type annotations * Type refinements now work on generic table indexing with a string literal * Type refinements will properly track potentially 'nil' values (like t[x] for a missing key) and their further refinements * Internal top table type is now isomorphic to `{}` which fixes issues when `typeof(v) == 'table'` type refinement is handled * References to non-existent types in type annotations no longer resolve to 'error' type like in old solver * Improved handling of class unions in property access expressions * Fixed default type packs * Unsealed tables can now have metatables * Restored expected types for function arguments And for native code generation: * Added min and max IR instructions mapping to vminsd/vmaxsd on x64 * We now speculatively extract direct execution fast-paths based on expected types of expressions which provides better optimization opportunities inside a single basic block * Translated existing math fastcalls to IR form to improve tag guard removal and constant propagation
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)))
{
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};
}
else if (AstStatRepeat* statRepeat = node->as<AstStatRepeat>(); statRepeat && statRepeat->condition->is<AstExprError>())
return autocompleteExpression(sourceModule, *module, builtinTypes, typeArena, ancestry, position);
2022-07-07 18:22:39 -07:00
else if (AstStatRepeat* statRepeat = extractStat<AstStatRepeat>(ancestry); statRepeat)
return {autocompleteStatement(sourceModule, *module, ancestry, position), ancestry, AutocompleteContext::Statement};
else if (AstExprTable* exprTable = parent->as<AstExprTable>();
exprTable && (node->is<AstExprGlobal>() || node->is<AstExprConstantString>() || node->is<AstExprInterpString>()))
{
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)
{
if (auto it = module->astExpectedTypes.find(exprTable))
{
auto result = autocompleteProps(*module, typeArena, builtinTypes, *it, PropIndexType::Key, ancestry);
if (auto nodeIt = module->astExpectedTypes.find(node->asExpr()))
autocompleteStringSingleton(*nodeIt, !node->is<AstExprConstantString>(), node, position, result);
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)
{
autocompleteStringSingleton(ttv->indexer->indexType, false, node, position, result);
}
}
// 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));
}
// If we know for sure that a key is being written, do not offer general expression suggestions
if (!key)
autocompleteExpression(sourceModule, *module, builtinTypes, typeArena, ancestry, position, result);
return {result, ancestry, AutocompleteContext::Property};
}
break;
}
}
}
else if (AstExprTable* exprTable = node->as<AstExprTable>(); exprTable && FFlag::LuauAutocompleteTableKeysNoInitialCharacter)
{
AutocompleteEntryMap result;
if (auto it = module->astExpectedTypes.find(exprTable))
{
result = autocompleteProps(*module, typeArena, builtinTypes, *it, PropIndexType::Key, ancestry);
// If the key type is a union of singleton strings,
// suggest those too.
if (auto ttv = get<TableType>(follow(*it)); ttv && ttv->indexer)
{
autocompleteStringSingleton(ttv->indexer->indexType, false, node, position, result);
}
// 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));
}
}
// Also offer general expression suggestions
autocompleteExpression(sourceModule, *module, builtinTypes, typeArena, ancestry, position, result);
return {result, ancestry, AutocompleteContext::Property};
}
else if (isIdentifier(node) && (parent->is<AstStatExpr>() || parent->is<AstStatError>()))
return {autocompleteStatement(sourceModule, *module, ancestry, position), ancestry, AutocompleteContext::Statement};
2022-07-07 18:22:39 -07:00
if (std::optional<AutocompleteEntryMap> ret = autocompleteStringParams(sourceModule, module, ancestry, position, callback))
{
return {*ret, ancestry, AutocompleteContext::String};
}
else if (node->is<AstExprConstantString>() || isSimpleInterpolatedString(node))
{
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()))
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)
{
2022-07-07 18:22:39 -07:00
if (auto idxExpr = ancestry.at(ancestry.size() - 2)->as<AstExprIndexExpr>())
{
if (auto it = module->astTypes.find(idxExpr->expr))
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)
{
2022-04-07 14:29:01 -07:00
if (auto it = module->astTypes.find(node == binExpr->left ? binExpr->right : binExpr->left))
autocompleteStringSingleton(*it, false, node, position, result);
}
}
}
2022-04-07 14:29:01 -07:00
return {result, ancestry, AutocompleteContext::String};
}
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};
}
if (node->is<AstExprConstantNumber>())
return {};
if (node->asExpr())
Sync to upstream/release/588 (#992) Type checker/autocomplete: * `Luau::autocomplete` no longer performs typechecking internally, make sure to run `Frontend::check` before performing autocomplete requests * Autocomplete string suggestions without "" are now only suggested inside the "" * Autocomplete suggestions now include `function (anonymous autofilled)` key with a full suggestion for the function expression (with arguments included) stored in `AutocompleteEntry::insertText` * `AutocompleteEntry::indexedWithSelf` is provided for function call suggestions made with `:` * Cyclic modules now see each other type exports as `any` to prevent memory use-after-free (similar to module return type) Runtime: * Updated inline/loop unroll cost model to better handle assignments (Fixes https://github.com/Roblox/luau/issues/978) * `math.noise` speed was improved by ~30% * `table.concat` speed was improved by ~5-7% * `tonumber` and `tostring` now have fastcall paths that execute ~1.5x and ~2.5x faster respectively (fixes #777) * Fixed crash in `luaL_typename` when index refers to a non-existing value * Fixed potential out of memory scenario when using `string.sub` or `string.char` in a loop * Fixed behavior of some fastcall builtins when called without arguments under -O2 to match original functions * Support for native code execution in VM is now enabled by default (note: native code still has to be generated explicitly) * `Codegen::compile` now accepts `CodeGen_OnlyNativeModules` flag. When set, only modules that have a `--!native` hot-comment at the top will be compiled to native code In our new typechecker: * Generic type packs are no longer considered to be variadic during unification * Timeout and cancellation now works in new solver * Fixed false positive errors around 'table' and 'function' type refinements * Table literals now use covariant unification rules. This is sound since literal has no type specified and has no aliases * Fixed issues with blocked types escaping the constraint solver * Fixed more places where error messages that should've been suppressed were still reported * Fixed errors when iterating over a top table type In our native code generation (jit): * 'DebugLuauAbortingChecks' flag is now supported on A64 * LOP_NEWCLOSURE has been translated to IR
2023-07-28 08:13:53 -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;
Sync to upstream/release/588 (#992) Type checker/autocomplete: * `Luau::autocomplete` no longer performs typechecking internally, make sure to run `Frontend::check` before performing autocomplete requests * Autocomplete string suggestions without "" are now only suggested inside the "" * Autocomplete suggestions now include `function (anonymous autofilled)` key with a full suggestion for the function expression (with arguments included) stored in `AutocompleteEntry::insertText` * `AutocompleteEntry::indexedWithSelf` is provided for function call suggestions made with `:` * Cyclic modules now see each other type exports as `any` to prevent memory use-after-free (similar to module return type) Runtime: * Updated inline/loop unroll cost model to better handle assignments (Fixes https://github.com/Roblox/luau/issues/978) * `math.noise` speed was improved by ~30% * `table.concat` speed was improved by ~5-7% * `tonumber` and `tostring` now have fastcall paths that execute ~1.5x and ~2.5x faster respectively (fixes #777) * Fixed crash in `luaL_typename` when index refers to a non-existing value * Fixed potential out of memory scenario when using `string.sub` or `string.char` in a loop * Fixed behavior of some fastcall builtins when called without arguments under -O2 to match original functions * Support for native code execution in VM is now enabled by default (note: native code still has to be generated explicitly) * `Codegen::compile` now accepts `CodeGen_OnlyNativeModules` flag. When set, only modules that have a `--!native` hot-comment at the top will be compiled to native code In our new typechecker: * Generic type packs are no longer considered to be variadic during unification * Timeout and cancellation now works in new solver * Fixed false positive errors around 'table' and 'function' type refinements * Table literals now use covariant unification rules. This is sound since literal has no type specified and has no aliases * Fixed issues with blocked types escaping the constraint solver * Fixed more places where error messages that should've been suppressed were still reported * Fixed errors when iterating over a top table type In our native code generation (jit): * 'DebugLuauAbortingChecks' flag is now supported on A64 * LOP_NEWCLOSURE has been translated to IR
2023-07-28 08:13:53 -07:00
}
else if (node->asStat())
return {autocompleteStatement(sourceModule, *module, ancestry, position), ancestry, AutocompleteContext::Statement};
return {};
}
AutocompleteResult autocomplete(Frontend& frontend, const ModuleName& moduleName, Position position, StringCompletionCallback callback)
{
const SourceModule* sourceModule = frontend.getSourceModule(moduleName);
if (!sourceModule)
return {};
ModulePtr module = frontend.moduleResolverForAutocomplete.getModule(moduleName);
if (!module)
return {};
NotNull<BuiltinTypes> builtinTypes = frontend.builtinTypes;
Scope* globalScope = frontend.globalsForAutocomplete.globalScope.get();
TypeArena typeArena;
return autocomplete(*sourceModule, module, builtinTypes, &typeArena, globalScope, position, callback);
}
} // namespace Luau