luau/Analysis/src/TypeAttach.cpp
Andy Friesen 74c532053f
Sync to upstream/release/604 (#1106)
New Solver

* New algorithm for inferring the types of locals that have no
annotations. This
algorithm is very conservative by default, but is augmented with some
control
  flow awareness to handle most common scenarios.
* Fix bugs in type inference of tables
* Improve performance of by switching out standard C++ containers for
`DenseHashMap`
* Infrastructure to support clearer error messages in strict mode

Native Code Generation

* Fix a lowering issue with buffer.writeu8 and 0x80-0xff values: A
constant
  argument wasn't truncated to the target type range and that causes an
  assertion failure in `build.mov`.
* Store full lightuserdata value in loop iteration protocol lowering
* Add analysis to compute function bytecode distribution
* This includes a class to analyze the bytecode operator distribution
per
function and a CLI tool that produces a JSON report. See the new cmake
      target `Luau.Bytecode.CLI`

---------

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: Lily Brown <lbrown@roblox.com>
Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
2023-11-17 10:46:18 -08:00

608 lines
20 KiB
C++

// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/TypeAttach.h"
#include "Luau/Error.h"
#include "Luau/Module.h"
#include "Luau/RecursionCounter.h"
#include "Luau/Scope.h"
#include "Luau/ToString.h"
#include "Luau/TypeInfer.h"
#include "Luau/TypePack.h"
#include "Luau/Type.h"
#include "Luau/TypeFamily.h"
#include <string>
static char* allocateString(Luau::Allocator& allocator, std::string_view contents)
{
char* result = (char*)allocator.allocate(contents.size() + 1);
memcpy(result, contents.data(), contents.size());
result[contents.size()] = '\0';
return result;
}
template<typename... Data>
static char* allocateString(Luau::Allocator& allocator, const char* format, Data... data)
{
int len = snprintf(nullptr, 0, format, data...);
char* result = (char*)allocator.allocate(len + 1);
snprintf(result, len + 1, format, data...);
return result;
}
using SyntheticNames = std::unordered_map<const void*, char*>;
namespace Luau
{
static const char* getName(Allocator* allocator, SyntheticNames* syntheticNames, const GenericType& gen)
{
size_t s = syntheticNames->size();
char*& n = (*syntheticNames)[&gen];
if (!n)
{
std::string str = gen.explicitName ? gen.name : generateName(s);
n = static_cast<char*>(allocator->allocate(str.size() + 1));
strcpy(n, str.c_str());
}
return n;
}
static const char* getName(Allocator* allocator, SyntheticNames* syntheticNames, const GenericTypePack& gen)
{
size_t s = syntheticNames->size();
char*& n = (*syntheticNames)[&gen];
if (!n)
{
std::string str = gen.explicitName ? gen.name : generateName(s);
n = static_cast<char*>(allocator->allocate(str.size() + 1));
strcpy(n, str.c_str());
}
return n;
}
class TypeRehydrationVisitor
{
std::map<void*, int> seen;
int count = 0;
bool hasSeen(const void* tv)
{
void* ttv = const_cast<void*>(tv);
auto it = seen.find(ttv);
if (it != seen.end() && it->second < count)
return true;
seen[ttv] = count;
return false;
}
public:
TypeRehydrationVisitor(Allocator* alloc, SyntheticNames* syntheticNames, const TypeRehydrationOptions& options = TypeRehydrationOptions())
: allocator(alloc)
, syntheticNames(syntheticNames)
, options(options)
{
}
AstTypePack* rehydrate(TypePackId tp);
AstType* operator()(const PrimitiveType& ptv)
{
switch (ptv.type)
{
case PrimitiveType::NilType:
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName("nil"), std::nullopt, Location());
case PrimitiveType::Boolean:
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName("boolean"), std::nullopt, Location());
case PrimitiveType::Number:
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName("number"), std::nullopt, Location());
case PrimitiveType::String:
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName("string"), std::nullopt, Location());
case PrimitiveType::Thread:
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName("thread"), std::nullopt, Location());
case PrimitiveType::Buffer:
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName("buffer"), std::nullopt, Location());
default:
return nullptr;
}
}
AstType* operator()(const BlockedType& btv)
{
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName("*blocked*"), std::nullopt, Location());
}
AstType* operator()(const PendingExpansionType& petv)
{
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName("*pending-expansion*"), std::nullopt, Location());
}
AstType* operator()(const SingletonType& stv)
{
if (const BooleanSingleton* bs = get<BooleanSingleton>(&stv))
return allocator->alloc<AstTypeSingletonBool>(Location(), bs->value);
else if (const StringSingleton* ss = get<StringSingleton>(&stv))
{
AstArray<char> value;
value.data = const_cast<char*>(ss->value.c_str());
value.size = strlen(value.data);
return allocator->alloc<AstTypeSingletonString>(Location(), value);
}
else
return nullptr;
}
AstType* operator()(const AnyType&)
{
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName("any"), std::nullopt, Location());
}
AstType* operator()(const TableType& ttv)
{
RecursionCounter counter(&count);
if (ttv.name && options.bannedNames.find(*ttv.name) == options.bannedNames.end())
{
AstArray<AstTypeOrPack> parameters;
parameters.size = ttv.instantiatedTypeParams.size();
parameters.data = static_cast<AstTypeOrPack*>(allocator->allocate(sizeof(AstTypeOrPack) * parameters.size));
for (size_t i = 0; i < ttv.instantiatedTypeParams.size(); ++i)
{
parameters.data[i] = {Luau::visit(*this, ttv.instantiatedTypeParams[i]->ty), {}};
}
for (size_t i = 0; i < ttv.instantiatedTypePackParams.size(); ++i)
{
parameters.data[i] = {{}, rehydrate(ttv.instantiatedTypePackParams[i])};
}
return allocator->alloc<AstTypeReference>(
Location(), std::nullopt, AstName(ttv.name->c_str()), std::nullopt, Location(), parameters.size != 0, parameters);
}
if (hasSeen(&ttv))
{
if (ttv.name)
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName(ttv.name->c_str()), std::nullopt, Location());
else
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName("<Cycle>"), std::nullopt, Location());
}
AstArray<AstTableProp> props;
props.size = ttv.props.size();
props.data = static_cast<AstTableProp*>(allocator->allocate(sizeof(AstTableProp) * props.size));
int idx = 0;
for (const auto& [propName, prop] : ttv.props)
{
RecursionCounter counter(&count);
char* name = allocateString(*allocator, propName);
props.data[idx].name = AstName(name);
props.data[idx].type = Luau::visit(*this, prop.type()->ty);
props.data[idx].location = Location();
idx++;
}
AstTableIndexer* indexer = nullptr;
if (ttv.indexer)
{
RecursionCounter counter(&count);
indexer = allocator->alloc<AstTableIndexer>();
indexer->indexType = Luau::visit(*this, ttv.indexer->indexType->ty);
indexer->resultType = Luau::visit(*this, ttv.indexer->indexResultType->ty);
}
return allocator->alloc<AstTypeTable>(Location(), props, indexer);
}
AstType* operator()(const MetatableType& mtv)
{
return Luau::visit(*this, mtv.table->ty);
}
AstType* operator()(const ClassType& ctv)
{
RecursionCounter counter(&count);
char* name = allocateString(*allocator, ctv.name);
if (!options.expandClassProps || hasSeen(&ctv) || count > 1)
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName{name}, std::nullopt, Location());
AstArray<AstTableProp> props;
props.size = ctv.props.size();
props.data = static_cast<AstTableProp*>(allocator->allocate(sizeof(AstTableProp) * props.size));
int idx = 0;
for (const auto& [propName, prop] : ctv.props)
{
char* name = allocateString(*allocator, propName);
props.data[idx].name = AstName{name};
props.data[idx].type = Luau::visit(*this, prop.type()->ty);
props.data[idx].location = Location();
idx++;
}
AstTableIndexer* indexer = nullptr;
if (ctv.indexer)
{
RecursionCounter counter(&count);
indexer = allocator->alloc<AstTableIndexer>();
indexer->indexType = Luau::visit(*this, ctv.indexer->indexType->ty);
indexer->resultType = Luau::visit(*this, ctv.indexer->indexResultType->ty);
}
return allocator->alloc<AstTypeTable>(Location(), props, indexer);
}
AstType* operator()(const FunctionType& ftv)
{
RecursionCounter counter(&count);
if (hasSeen(&ftv))
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName("<Cycle>"), std::nullopt, Location());
AstArray<AstGenericType> generics;
generics.size = ftv.generics.size();
generics.data = static_cast<AstGenericType*>(allocator->allocate(sizeof(AstGenericType) * generics.size));
size_t numGenerics = 0;
for (auto it = ftv.generics.begin(); it != ftv.generics.end(); ++it)
{
if (auto gtv = get<GenericType>(*it))
generics.data[numGenerics++] = {AstName(gtv->name.c_str()), Location(), nullptr};
}
AstArray<AstGenericTypePack> genericPacks;
genericPacks.size = ftv.genericPacks.size();
genericPacks.data = static_cast<AstGenericTypePack*>(allocator->allocate(sizeof(AstGenericTypePack) * genericPacks.size));
size_t numGenericPacks = 0;
for (auto it = ftv.genericPacks.begin(); it != ftv.genericPacks.end(); ++it)
{
if (auto gtv = get<GenericTypePack>(*it))
genericPacks.data[numGenericPacks++] = {AstName(gtv->name.c_str()), Location(), nullptr};
}
AstArray<AstType*> argTypes;
const auto& [argVector, argTail] = flatten(ftv.argTypes);
argTypes.size = argVector.size();
argTypes.data = static_cast<AstType**>(allocator->allocate(sizeof(AstType*) * argTypes.size));
for (size_t i = 0; i < argTypes.size; ++i)
{
RecursionCounter counter(&count);
argTypes.data[i] = Luau::visit(*this, (argVector[i])->ty);
}
AstTypePack* argTailAnnotation = nullptr;
if (argTail)
argTailAnnotation = rehydrate(*argTail);
AstArray<std::optional<AstArgumentName>> argNames;
argNames.size = ftv.argNames.size();
argNames.data = static_cast<std::optional<AstArgumentName>*>(allocator->allocate(sizeof(std::optional<AstArgumentName>) * argNames.size));
size_t i = 0;
for (const auto& el : ftv.argNames)
{
std::optional<AstArgumentName>* arg = &argNames.data[i++];
if (el)
new (arg) std::optional<AstArgumentName>(AstArgumentName(AstName(el->name.c_str()), el->location));
else
new (arg) std::optional<AstArgumentName>();
}
AstArray<AstType*> returnTypes;
const auto& [retVector, retTail] = flatten(ftv.retTypes);
returnTypes.size = retVector.size();
returnTypes.data = static_cast<AstType**>(allocator->allocate(sizeof(AstType*) * returnTypes.size));
for (size_t i = 0; i < returnTypes.size; ++i)
{
RecursionCounter counter(&count);
returnTypes.data[i] = Luau::visit(*this, (retVector[i])->ty);
}
AstTypePack* retTailAnnotation = nullptr;
if (retTail)
retTailAnnotation = rehydrate(*retTail);
return allocator->alloc<AstTypeFunction>(
Location(), generics, genericPacks, AstTypeList{argTypes, argTailAnnotation}, argNames, AstTypeList{returnTypes, retTailAnnotation});
}
AstType* operator()(const Unifiable::Error&)
{
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName("Unifiable<Error>"), std::nullopt, Location());
}
AstType* operator()(const GenericType& gtv)
{
return allocator->alloc<AstTypeReference>(
Location(), std::nullopt, AstName(getName(allocator, syntheticNames, gtv)), std::nullopt, Location());
}
AstType* operator()(const Unifiable::Bound<TypeId>& bound)
{
return Luau::visit(*this, bound.boundTo->ty);
}
AstType* operator()(const FreeType& ft)
{
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName("free"), std::nullopt, Location());
}
AstType* operator()(const LocalType& lt)
{
return Luau::visit(*this, lt.domain->ty);
}
AstType* operator()(const UnionType& uv)
{
AstArray<AstType*> unionTypes;
unionTypes.size = uv.options.size();
unionTypes.data = static_cast<AstType**>(allocator->allocate(sizeof(AstType*) * unionTypes.size));
for (size_t i = 0; i < unionTypes.size; ++i)
{
unionTypes.data[i] = Luau::visit(*this, uv.options[i]->ty);
}
return allocator->alloc<AstTypeUnion>(Location(), unionTypes);
}
AstType* operator()(const IntersectionType& uv)
{
AstArray<AstType*> intersectionTypes;
intersectionTypes.size = uv.parts.size();
intersectionTypes.data = static_cast<AstType**>(allocator->allocate(sizeof(AstType*) * intersectionTypes.size));
for (size_t i = 0; i < intersectionTypes.size; ++i)
{
intersectionTypes.data[i] = Luau::visit(*this, uv.parts[i]->ty);
}
return allocator->alloc<AstTypeIntersection>(Location(), intersectionTypes);
}
AstType* operator()(const LazyType& ltv)
{
if (TypeId unwrapped = ltv.unwrapped.load())
return Luau::visit(*this, unwrapped->ty);
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName("<Lazy?>"), std::nullopt, Location());
}
AstType* operator()(const UnknownType& ttv)
{
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName{"unknown"}, std::nullopt, Location());
}
AstType* operator()(const NeverType& ttv)
{
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName{"never"}, std::nullopt, Location());
}
AstType* operator()(const NegationType& ntv)
{
// FIXME: do the same thing we do with ErrorType
throw InternalCompilerError("Cannot convert NegationType into AstNode");
}
AstType* operator()(const TypeFamilyInstanceType& tfit)
{
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName{tfit.family->name.c_str()}, std::nullopt, Location());
}
private:
Allocator* allocator;
SyntheticNames* syntheticNames;
const TypeRehydrationOptions& options;
};
class TypePackRehydrationVisitor
{
public:
TypePackRehydrationVisitor(Allocator* allocator, SyntheticNames* syntheticNames, TypeRehydrationVisitor* typeVisitor)
: allocator(allocator)
, syntheticNames(syntheticNames)
, typeVisitor(typeVisitor)
{
LUAU_ASSERT(allocator);
LUAU_ASSERT(syntheticNames);
LUAU_ASSERT(typeVisitor);
}
AstTypePack* operator()(const BoundTypePack& btp) const
{
return Luau::visit(*this, btp.boundTo->ty);
}
AstTypePack* operator()(const BlockedTypePack& btp) const
{
return allocator->alloc<AstTypePackGeneric>(Location(), AstName("*blocked*"));
}
AstTypePack* operator()(const TypePack& tp) const
{
AstArray<AstType*> head;
head.size = tp.head.size();
head.data = static_cast<AstType**>(allocator->allocate(sizeof(AstType*) * tp.head.size()));
for (size_t i = 0; i < tp.head.size(); i++)
head.data[i] = Luau::visit(*typeVisitor, tp.head[i]->ty);
AstTypePack* tail = nullptr;
if (tp.tail)
tail = Luau::visit(*this, (*tp.tail)->ty);
return allocator->alloc<AstTypePackExplicit>(Location(), AstTypeList{head, tail});
}
AstTypePack* operator()(const VariadicTypePack& vtp) const
{
if (vtp.hidden)
return nullptr;
return allocator->alloc<AstTypePackVariadic>(Location(), Luau::visit(*typeVisitor, vtp.ty->ty));
}
AstTypePack* operator()(const GenericTypePack& gtp) const
{
return allocator->alloc<AstTypePackGeneric>(Location(), AstName(getName(allocator, syntheticNames, gtp)));
}
AstTypePack* operator()(const FreeTypePack& gtp) const
{
return allocator->alloc<AstTypePackGeneric>(Location(), AstName("free"));
}
AstTypePack* operator()(const Unifiable::Error&) const
{
return allocator->alloc<AstTypePackGeneric>(Location(), AstName("Unifiable<Error>"));
}
AstTypePack* operator()(const TypeFamilyInstanceTypePack& tfitp) const
{
return allocator->alloc<AstTypePackGeneric>(Location(), AstName(tfitp.family->name.c_str()));
}
private:
Allocator* allocator;
SyntheticNames* syntheticNames;
TypeRehydrationVisitor* typeVisitor;
};
AstTypePack* TypeRehydrationVisitor::rehydrate(TypePackId tp)
{
TypePackRehydrationVisitor tprv(allocator, syntheticNames, this);
return Luau::visit(tprv, tp->ty);
}
class TypeAttacher : public AstVisitor
{
public:
TypeAttacher(Module& checker, Luau::Allocator* alloc)
: module(checker)
, allocator(alloc)
{
}
ScopePtr getScope(const Location& loc)
{
Location scopeLocation;
ScopePtr scope = nullptr;
for (const auto& s : module.scopes)
{
if (s.first.encloses(loc))
{
if (!scope || scopeLocation.encloses(s.first))
{
scopeLocation = s.first;
scope = s.second;
}
}
}
return scope;
}
AstType* typeAst(std::optional<TypeId> type)
{
if (!type)
return nullptr;
return Luau::visit(TypeRehydrationVisitor(allocator, &syntheticNames), (*type)->ty);
}
AstArray<Luau::AstType*> typeAstPack(TypePackId type)
{
const auto& [v, tail] = flatten(type);
AstArray<AstType*> result;
result.size = v.size();
result.data = static_cast<AstType**>(allocator->allocate(sizeof(AstType*) * v.size()));
for (size_t i = 0; i < v.size(); ++i)
{
result.data[i] = Luau::visit(TypeRehydrationVisitor(allocator, &syntheticNames), v[i]->ty);
}
return result;
}
virtual bool visit(AstStatLocal* al) override
{
for (size_t i = 0; i < al->vars.size; ++i)
{
visitLocal(al->vars.data[i]);
}
return true;
}
virtual bool visitLocal(AstLocal* local)
{
AstType* annotation = local->annotation;
if (!annotation)
{
if (auto scope = getScope(local->location))
{
if (auto result = scope->lookup(local))
local->annotation = typeAst(*result);
}
}
return true;
}
virtual bool visit(AstExprLocal* al) override
{
return visitLocal(al->local);
}
virtual bool visit(AstStatFor* stat) override
{
visitLocal(stat->var);
return true;
}
virtual bool visit(AstStatForIn* stat) override
{
for (size_t i = 0; i < stat->vars.size; ++i)
visitLocal(stat->vars.data[i]);
return true;
}
virtual bool visit(AstExprFunction* fn) override
{
// TODO: add generics if the inferred type of the function is generic CLI-39908
for (size_t i = 0; i < fn->args.size; ++i)
{
AstLocal* arg = fn->args.data[i];
visitLocal(arg);
}
if (!fn->returnAnnotation)
{
if (auto result = getScope(fn->body->location))
{
TypePackId ret = result->returnType;
AstTypePack* variadicAnnotation = nullptr;
const auto& [v, tail] = flatten(ret);
if (tail)
variadicAnnotation = TypeRehydrationVisitor(allocator, &syntheticNames).rehydrate(*tail);
fn->returnAnnotation = AstTypeList{typeAstPack(ret), variadicAnnotation};
}
}
return true;
}
private:
Module& module;
Allocator* allocator;
SyntheticNames syntheticNames;
};
void attachTypeData(SourceModule& source, Module& result)
{
TypeAttacher ta(result, source.allocator.get());
source.root->visit(&ta);
}
AstType* rehydrateAnnotation(TypeId type, Allocator* allocator, const TypeRehydrationOptions& options)
{
SyntheticNames syntheticNames;
return Luau::visit(TypeRehydrationVisitor(allocator, &syntheticNames, options), type->ty);
}
} // namespace Luau