luau/Analysis/src/DcrLogger.cpp
Junseo Yoo ce8495a69e
Sync to upstream/release/637 (#1354)
# What's Changed?

- Code refactoring with a new clang-format
- More bug fixes / test case fixes in the new solver

## New Solver

- More precise telemetry collection of `any` types
- Simplification of two completely disjoint tables combines them into a
single table that inherits all properties / indexers
- Refining a `never & <anything>` does not produce type family types nor
constraints
- Silence "inference failed to complete" error when it is the only error
reported

---
### Internal Contributors

Co-authored-by: Aaron Weiss <aaronweiss@roblox.com>
Co-authored-by: Andy Friesen <afriesen@roblox.com>
Co-authored-by: Dibri Nsofor <dnsofor@roblox.com>
Co-authored-by: Jeremy Yoo <jyoo@roblox.com>
Co-authored-by: Vighnesh Vijay <vvijay@roblox.com>
Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>

---------

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: 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-08-02 07:30:04 -07:00

493 lines
13 KiB
C++

// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/DcrLogger.h"
#include <algorithm>
#include "Luau/JsonEmitter.h"
namespace Luau
{
template<typename T>
static std::string toPointerId(const T* ptr)
{
return std::to_string(reinterpret_cast<size_t>(ptr));
}
static std::string toPointerId(NotNull<const Constraint> ptr)
{
return std::to_string(reinterpret_cast<size_t>(ptr.get()));
}
namespace Json
{
template<typename T>
void write(JsonEmitter& emitter, const T* ptr)
{
write(emitter, toPointerId(ptr));
}
void write(JsonEmitter& emitter, NotNull<const Constraint> ptr)
{
write(emitter, toPointerId(ptr));
}
void write(JsonEmitter& emitter, const Location& location)
{
ArrayEmitter a = emitter.writeArray();
a.writeValue(location.begin.line);
a.writeValue(location.begin.column);
a.writeValue(location.end.line);
a.writeValue(location.end.column);
a.finish();
}
void write(JsonEmitter& emitter, const ErrorSnapshot& snapshot)
{
ObjectEmitter o = emitter.writeObject();
o.writePair("message", snapshot.message);
o.writePair("location", snapshot.location);
o.finish();
}
void write(JsonEmitter& emitter, const BindingSnapshot& snapshot)
{
ObjectEmitter o = emitter.writeObject();
o.writePair("typeId", snapshot.typeId);
o.writePair("typeString", snapshot.typeString);
o.writePair("location", snapshot.location);
o.finish();
}
void write(JsonEmitter& emitter, const TypeBindingSnapshot& snapshot)
{
ObjectEmitter o = emitter.writeObject();
o.writePair("typeId", snapshot.typeId);
o.writePair("typeString", snapshot.typeString);
o.finish();
}
template<typename K, typename V>
void write(JsonEmitter& emitter, const DenseHashMap<const K*, V>& map)
{
ObjectEmitter o = emitter.writeObject();
for (const auto& [k, v] : map)
o.writePair(toPointerId(k), v);
o.finish();
}
void write(JsonEmitter& emitter, const ExprTypesAtLocation& tys)
{
ObjectEmitter o = emitter.writeObject();
o.writePair("location", tys.location);
o.writePair("ty", toPointerId(tys.ty));
if (tys.expectedTy)
o.writePair("expectedTy", toPointerId(*tys.expectedTy));
o.finish();
}
void write(JsonEmitter& emitter, const AnnotationTypesAtLocation& tys)
{
ObjectEmitter o = emitter.writeObject();
o.writePair("location", tys.location);
o.writePair("resolvedTy", toPointerId(tys.resolvedTy));
o.finish();
}
void write(JsonEmitter& emitter, const ConstraintGenerationLog& log)
{
ObjectEmitter o = emitter.writeObject();
o.writePair("source", log.source);
o.writePair("errors", log.errors);
o.writePair("exprTypeLocations", log.exprTypeLocations);
o.writePair("annotationTypeLocations", log.annotationTypeLocations);
o.finish();
}
void write(JsonEmitter& emitter, const ScopeSnapshot& snapshot)
{
ObjectEmitter o = emitter.writeObject();
o.writePair("bindings", snapshot.bindings);
o.writePair("typeBindings", snapshot.typeBindings);
o.writePair("typePackBindings", snapshot.typePackBindings);
o.writePair("children", snapshot.children);
o.finish();
}
void write(JsonEmitter& emitter, const ConstraintBlock& block)
{
ObjectEmitter o = emitter.writeObject();
o.writePair("stringification", block.stringification);
auto go = [&o](auto&& t)
{
using T = std::decay_t<decltype(t)>;
o.writePair("id", toPointerId(t));
if constexpr (std::is_same_v<T, TypeId>)
{
o.writePair("kind", "type");
}
else if constexpr (std::is_same_v<T, TypePackId>)
{
o.writePair("kind", "typePack");
}
else if constexpr (std::is_same_v<T, NotNull<const Constraint>>)
{
o.writePair("kind", "constraint");
}
else
static_assert(always_false_v<T>, "non-exhaustive possibility switch");
};
visit(go, block.target);
o.finish();
}
void write(JsonEmitter& emitter, const ConstraintSnapshot& snapshot)
{
ObjectEmitter o = emitter.writeObject();
o.writePair("stringification", snapshot.stringification);
o.writePair("location", snapshot.location);
o.writePair("blocks", snapshot.blocks);
o.finish();
}
void write(JsonEmitter& emitter, const BoundarySnapshot& snapshot)
{
ObjectEmitter o = emitter.writeObject();
o.writePair("rootScope", snapshot.rootScope);
o.writePair("unsolvedConstraints", snapshot.unsolvedConstraints);
o.writePair("typeStrings", snapshot.typeStrings);
o.finish();
}
void write(JsonEmitter& emitter, const StepSnapshot& snapshot)
{
ObjectEmitter o = emitter.writeObject();
o.writePair("currentConstraint", snapshot.currentConstraint);
o.writePair("forced", snapshot.forced);
o.writePair("unsolvedConstraints", snapshot.unsolvedConstraints);
o.writePair("rootScope", snapshot.rootScope);
o.writePair("typeStrings", snapshot.typeStrings);
o.finish();
}
void write(JsonEmitter& emitter, const TypeSolveLog& log)
{
ObjectEmitter o = emitter.writeObject();
o.writePair("initialState", log.initialState);
o.writePair("stepStates", log.stepStates);
o.writePair("finalState", log.finalState);
o.finish();
}
void write(JsonEmitter& emitter, const TypeCheckLog& log)
{
ObjectEmitter o = emitter.writeObject();
o.writePair("errors", log.errors);
o.finish();
}
} // namespace Json
static ScopeSnapshot snapshotScope(const Scope* scope, ToStringOptions& opts)
{
std::unordered_map<Name, BindingSnapshot> bindings;
std::unordered_map<Name, TypeBindingSnapshot> typeBindings;
std::unordered_map<Name, TypeBindingSnapshot> typePackBindings;
std::vector<ScopeSnapshot> children;
for (const auto& [name, binding] : scope->bindings)
{
std::string id = std::to_string(reinterpret_cast<size_t>(binding.typeId));
ToStringResult result = toStringDetailed(binding.typeId, opts);
bindings[name.c_str()] = BindingSnapshot{
id,
result.name,
binding.location,
};
}
for (const auto& [name, tf] : scope->exportedTypeBindings)
{
std::string id = std::to_string(reinterpret_cast<size_t>(tf.type));
typeBindings[name] = TypeBindingSnapshot{
id,
toString(tf.type, opts),
};
}
for (const auto& [name, tf] : scope->privateTypeBindings)
{
std::string id = std::to_string(reinterpret_cast<size_t>(tf.type));
typeBindings[name] = TypeBindingSnapshot{
id,
toString(tf.type, opts),
};
}
for (const auto& [name, tp] : scope->privateTypePackBindings)
{
std::string id = std::to_string(reinterpret_cast<size_t>(tp));
typePackBindings[name] = TypeBindingSnapshot{
id,
toString(tp, opts),
};
}
for (const auto& child : scope->children)
{
children.push_back(snapshotScope(child.get(), opts));
}
return ScopeSnapshot{
bindings,
typeBindings,
typePackBindings,
children,
};
}
std::string DcrLogger::compileOutput()
{
Json::JsonEmitter emitter;
Json::ObjectEmitter o = emitter.writeObject();
o.writePair("generation", generationLog);
o.writePair("solve", solveLog);
o.writePair("check", checkLog);
o.finish();
return emitter.str();
}
void DcrLogger::captureSource(std::string source)
{
generationLog.source = std::move(source);
}
void DcrLogger::captureGenerationModule(const ModulePtr& module)
{
generationLog.exprTypeLocations.reserve(module->astTypes.size());
for (const auto& [expr, ty] : module->astTypes)
{
ExprTypesAtLocation tys;
tys.location = expr->location;
tys.ty = ty;
if (auto expectedTy = module->astExpectedTypes.find(expr))
tys.expectedTy = *expectedTy;
generationLog.exprTypeLocations.push_back(tys);
}
generationLog.annotationTypeLocations.reserve(module->astResolvedTypes.size());
for (const auto& [annot, ty] : module->astResolvedTypes)
{
AnnotationTypesAtLocation tys;
tys.location = annot->location;
tys.resolvedTy = ty;
generationLog.annotationTypeLocations.push_back(tys);
}
}
void DcrLogger::captureGenerationError(const TypeError& error)
{
std::string stringifiedError = toString(error);
generationLog.errors.push_back(ErrorSnapshot{
/* message */ stringifiedError,
/* location */ error.location,
});
}
void DcrLogger::pushBlock(NotNull<const Constraint> constraint, TypeId block)
{
constraintBlocks[constraint].push_back(block);
}
void DcrLogger::pushBlock(NotNull<const Constraint> constraint, TypePackId block)
{
constraintBlocks[constraint].push_back(block);
}
void DcrLogger::pushBlock(NotNull<const Constraint> constraint, NotNull<const Constraint> block)
{
constraintBlocks[constraint].push_back(block);
}
void DcrLogger::popBlock(TypeId block)
{
for (auto& [_, list] : constraintBlocks)
{
list.erase(std::remove(list.begin(), list.end(), block), list.end());
}
}
void DcrLogger::popBlock(TypePackId block)
{
for (auto& [_, list] : constraintBlocks)
{
list.erase(std::remove(list.begin(), list.end(), block), list.end());
}
}
void DcrLogger::popBlock(NotNull<const Constraint> block)
{
for (auto& [_, list] : constraintBlocks)
{
list.erase(std::remove(list.begin(), list.end(), block), list.end());
}
}
static void snapshotTypeStrings(
const std::vector<ExprTypesAtLocation>& interestedExprs,
const std::vector<AnnotationTypesAtLocation>& interestedAnnots,
DenseHashMap<const void*, std::string>& map,
ToStringOptions& opts
)
{
for (const ExprTypesAtLocation& tys : interestedExprs)
{
map[tys.ty] = toString(tys.ty, opts);
if (tys.expectedTy)
map[*tys.expectedTy] = toString(*tys.expectedTy, opts);
}
for (const AnnotationTypesAtLocation& tys : interestedAnnots)
{
map[tys.resolvedTy] = toString(tys.resolvedTy, opts);
}
}
void DcrLogger::captureBoundaryState(
BoundarySnapshot& target,
const Scope* rootScope,
const std::vector<NotNull<const Constraint>>& unsolvedConstraints
)
{
target.rootScope = snapshotScope(rootScope, opts);
target.unsolvedConstraints.clear();
for (NotNull<const Constraint> c : unsolvedConstraints)
{
target.unsolvedConstraints[c.get()] = {
toString(*c.get(), opts),
c->location,
snapshotBlocks(c),
};
}
snapshotTypeStrings(generationLog.exprTypeLocations, generationLog.annotationTypeLocations, target.typeStrings, opts);
}
void DcrLogger::captureInitialSolverState(const Scope* rootScope, const std::vector<NotNull<const Constraint>>& unsolvedConstraints)
{
captureBoundaryState(solveLog.initialState, rootScope, unsolvedConstraints);
}
StepSnapshot DcrLogger::prepareStepSnapshot(
const Scope* rootScope,
NotNull<const Constraint> current,
bool force,
const std::vector<NotNull<const Constraint>>& unsolvedConstraints
)
{
ScopeSnapshot scopeSnapshot = snapshotScope(rootScope, opts);
DenseHashMap<const Constraint*, ConstraintSnapshot> constraints{nullptr};
for (NotNull<const Constraint> c : unsolvedConstraints)
{
constraints[c.get()] = {
toString(*c.get(), opts),
c->location,
snapshotBlocks(c),
};
}
DenseHashMap<const void*, std::string> typeStrings{nullptr};
snapshotTypeStrings(generationLog.exprTypeLocations, generationLog.annotationTypeLocations, typeStrings, opts);
return StepSnapshot{
current,
force,
std::move(constraints),
scopeSnapshot,
std::move(typeStrings),
};
}
void DcrLogger::commitStepSnapshot(StepSnapshot snapshot)
{
solveLog.stepStates.push_back(std::move(snapshot));
}
void DcrLogger::captureFinalSolverState(const Scope* rootScope, const std::vector<NotNull<const Constraint>>& unsolvedConstraints)
{
captureBoundaryState(solveLog.finalState, rootScope, unsolvedConstraints);
}
void DcrLogger::captureTypeCheckError(const TypeError& error)
{
std::string stringifiedError = toString(error);
checkLog.errors.push_back(ErrorSnapshot{
/* message */ stringifiedError,
/* location */ error.location,
});
}
std::vector<ConstraintBlock> DcrLogger::snapshotBlocks(NotNull<const Constraint> c)
{
auto it = constraintBlocks.find(c);
if (it == constraintBlocks.end())
{
return {};
}
std::vector<ConstraintBlock> snapshot;
for (const ConstraintBlockTarget& target : it->second)
{
if (const TypeId* ty = get_if<TypeId>(&target))
{
snapshot.push_back({
*ty,
toString(*ty, opts),
});
}
else if (const TypePackId* tp = get_if<TypePackId>(&target))
{
snapshot.push_back({
*tp,
toString(*tp, opts),
});
}
else if (const NotNull<const Constraint>* c = get_if<NotNull<const Constraint>>(&target))
{
snapshot.push_back({
*c,
toString(*(c->get()), opts),
});
}
else
{
LUAU_ASSERT(0);
}
}
return snapshot;
}
} // namespace Luau