luau/Analysis/src/Transpiler.cpp
Varun Saini 5f42e63a73
Sync to upstream/release/666 (#1747)
Another week, another release. Happy spring! 🌷 

## New Type Solver

- Add typechecking and autocomplete support for user-defined type
functions!
- Improve the display of type paths, making type mismatch errors far
more human-readable.
- Enhance various aspects of the `index` type function: support function
type metamethods, fix crashes involving cyclic metatables, and forward
`any` types through the type function.
- Fix incorrect subtyping results involving the `buffer` type.
- Fix crashes related to typechecking anonymous functions in nonstrict
mode.

## AST

- Retain source information for type packs, functions, and type
functions.
- Introduce `AstTypeOptional` to differentiate `T?` from `T | nil` in
the AST.
- Prevent the transpiler from advancing before tokens when the AST has
errors.

## Autocomplete

- Introduce demand-based cloning and better module isolation for
fragment autocomplete, leading to a substantial speedup in performance.
- Guard against recursive unions in `autocompleteProps`.

## Miscellaneous

- #1720 (thank you!)

## Internal Contributors

Co-authored-by: Andy Friesen <afriesen@roblox.com>
Co-authored-by: Ariel Weiss <aaronweiss@roblox.com>
Co-authored-by: Hunter Goldstein <hgoldstein@roblox.com>
Co-authored-by: Talha Pathan <tpathan@roblox.com>
Co-authored-by: Vighnesh Vijay <vvijay@roblox.com>
Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
2025-03-21 14:43:00 -07:00

2886 lines
89 KiB
C++

// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/Transpiler.h"
#include "Luau/Parser.h"
#include "Luau/StringUtils.h"
#include "Luau/Common.h"
#include <algorithm>
#include <memory>
#include <limits>
#include <math.h>
LUAU_FASTFLAG(LuauStoreCSTData)
LUAU_FASTFLAG(LuauExtendStatEndPosWithSemicolon)
LUAU_FASTFLAG(LuauAstTypeGroup3)
LUAU_FASTFLAG(LuauFixDoBlockEndLocation)
LUAU_FASTFLAG(LuauParseOptionalAsNode)
namespace
{
bool isIdentifierStartChar(char c)
{
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_';
}
bool isDigit(char c)
{
return c >= '0' && c <= '9';
}
bool isIdentifierChar(char c)
{
return isIdentifierStartChar(c) || isDigit(c);
}
const std::vector<std::string> keywords = {"and", "break", "do", "else", "elseif", "end", "false", "for", "function", "if", "in",
"local", "nil", "not", "or", "repeat", "return", "then", "true", "until", "while"};
} // namespace
namespace Luau
{
struct Writer
{
virtual ~Writer() {}
virtual void advance(const Position&) = 0;
virtual void newline() = 0;
virtual void space() = 0;
virtual void maybeSpace(const Position& newPos, int reserve) = 0;
virtual void write(std::string_view) = 0;
virtual void writeMultiline(std::string_view) = 0;
virtual void identifier(std::string_view name) = 0;
virtual void keyword(std::string_view) = 0;
virtual void symbol(std::string_view) = 0;
virtual void literal(std::string_view) = 0;
virtual void string(std::string_view) = 0;
virtual void sourceString(std::string_view, CstExprConstantString::QuoteStyle quoteStyle, unsigned int blockDepth) = 0;
};
struct StringWriter : Writer
{
std::string ss;
Position pos{0, 0};
char lastChar = '\0'; // used to determine whether we need to inject an extra space to preserve grammatical correctness.
const std::string& str() const
{
return ss;
}
void advance(const Position& newPos) override
{
while (pos.line < newPos.line)
newline();
if (pos.column < newPos.column)
write(std::string(newPos.column - pos.column, ' '));
}
void maybeSpace(const Position& newPos, int reserve) override
{
if (pos.column + reserve < newPos.column)
space();
}
void newline() override
{
ss += '\n';
pos.column = 0;
++pos.line;
lastChar = '\n';
}
void space() override
{
ss += ' ';
++pos.column;
lastChar = ' ';
}
void writeMultiline(std::string_view s) override
{
if (s.empty())
return;
ss.append(s.data(), s.size());
lastChar = s[s.size() - 1];
size_t index = 0;
size_t numLines = 0;
while (true)
{
auto newlinePos = s.find('\n', index);
if (newlinePos == std::string::npos)
break;
numLines++;
index = newlinePos + 1;
}
pos.line += unsigned(numLines);
if (numLines > 0)
pos.column = unsigned(s.size()) - unsigned(index);
else
pos.column += unsigned(s.size());
}
void write(std::string_view s) override
{
if (s.empty())
return;
ss.append(s.data(), s.size());
pos.column += unsigned(s.size());
lastChar = s[s.size() - 1];
}
void write(char c)
{
ss += c;
pos.column += 1;
lastChar = c;
}
void identifier(std::string_view s) override
{
if (s.empty())
return;
if (isIdentifierChar(lastChar))
space();
write(s);
}
void keyword(std::string_view s) override
{
if (s.empty())
return;
if (isIdentifierChar(lastChar))
space();
write(s);
}
void symbol(std::string_view s) override
{
if (FFlag::LuauStoreCSTData)
{
write(s);
}
else
{
if (isDigit(lastChar) && s[0] == '.')
space();
write(s);
}
}
void literal(std::string_view s) override
{
if (s.empty())
return;
else if (isIdentifierChar(lastChar) && isDigit(s[0]))
space();
write(s);
}
void string(std::string_view s) override
{
char quote = '\'';
if (std::string::npos != s.find(quote))
quote = '\"';
write(quote);
write(escape(s));
write(quote);
}
void sourceString(std::string_view s, CstExprConstantString::QuoteStyle quoteStyle, unsigned int blockDepth) override
{
if (quoteStyle == CstExprConstantString::QuotedRaw)
{
auto blocks = std::string(blockDepth, '=');
write('[');
write(blocks);
write('[');
writeMultiline(s);
write(']');
write(blocks);
write(']');
}
else
{
LUAU_ASSERT(blockDepth == 0);
char quote = '"';
switch (quoteStyle)
{
case CstExprConstantString::QuotedDouble:
quote = '"';
break;
case CstExprConstantString::QuotedSingle:
quote = '\'';
break;
case CstExprConstantString::QuotedInterp:
quote = '`';
break;
default:
LUAU_ASSERT(!"Unhandled quote type");
}
write(quote);
writeMultiline(s);
write(quote);
}
}
};
class CommaSeparatorInserter
{
public:
explicit CommaSeparatorInserter(Writer& w, const Position* commaPosition = nullptr)
: first(true)
, writer(w)
, commaPosition(commaPosition)
{
}
void operator()()
{
if (first)
first = !first;
else
{
if (FFlag::LuauStoreCSTData && commaPosition)
{
writer.advance(*commaPosition);
commaPosition++;
}
writer.symbol(",");
}
}
private:
bool first;
Writer& writer;
const Position* commaPosition;
};
class ArgNameInserter
{
public:
ArgNameInserter(Writer& w, AstArray<std::optional<AstArgumentName>> names, AstArray<std::optional<Position>> colonPositions)
: writer(w)
, names(names)
, colonPositions(colonPositions)
{
}
void operator()()
{
if (idx < names.size)
{
const auto name = names.data[idx];
if (name.has_value())
{
writer.advance(name->second.begin);
writer.identifier(name->first.value);
if (idx < colonPositions.size)
{
LUAU_ASSERT(colonPositions.data[idx].has_value());
writer.advance(*colonPositions.data[idx]);
}
writer.symbol(":");
}
}
idx++;
}
private:
Writer& writer;
AstArray<std::optional<AstArgumentName>> names;
AstArray<std::optional<Position>> colonPositions;
size_t idx = 0;
};
struct Printer_DEPRECATED
{
explicit Printer_DEPRECATED(Writer& writer)
: writer(writer)
{
}
bool writeTypes = false;
Writer& writer;
void visualize(const AstLocal& local)
{
advance(local.location.begin);
writer.identifier(local.name.value);
if (writeTypes && local.annotation)
{
writer.symbol(":");
visualizeTypeAnnotation(*local.annotation);
}
}
void visualizeTypePackAnnotation(const AstTypePack& annotation, bool forVarArg)
{
advance(annotation.location.begin);
if (const AstTypePackVariadic* variadicTp = annotation.as<AstTypePackVariadic>())
{
if (!forVarArg)
writer.symbol("...");
visualizeTypeAnnotation(*variadicTp->variadicType);
}
else if (const AstTypePackGeneric* genericTp = annotation.as<AstTypePackGeneric>())
{
writer.symbol(genericTp->genericName.value);
writer.symbol("...");
}
else if (const AstTypePackExplicit* explicitTp = annotation.as<AstTypePackExplicit>())
{
LUAU_ASSERT(!forVarArg);
visualizeTypeList(explicitTp->typeList, true);
}
else
{
LUAU_ASSERT(!"Unknown TypePackAnnotation kind");
}
}
void visualizeTypeList(const AstTypeList& list, bool unconditionallyParenthesize)
{
size_t typeCount = list.types.size + (list.tailType != nullptr ? 1 : 0);
if (typeCount == 0)
{
writer.symbol("(");
writer.symbol(")");
}
else if (typeCount == 1)
{
bool shouldParenthesize = unconditionallyParenthesize && (list.types.size == 0 || !list.types.data[0]->is<AstTypeGroup>());
if (FFlag::LuauAstTypeGroup3 ? shouldParenthesize : unconditionallyParenthesize)
writer.symbol("(");
// Only variadic tail
if (list.types.size == 0)
{
visualizeTypePackAnnotation(*list.tailType, false);
}
else
{
visualizeTypeAnnotation(*list.types.data[0]);
}
if (FFlag::LuauAstTypeGroup3 ? shouldParenthesize : unconditionallyParenthesize)
writer.symbol(")");
}
else
{
writer.symbol("(");
bool first = true;
for (const auto& el : list.types)
{
if (first)
first = false;
else
writer.symbol(",");
visualizeTypeAnnotation(*el);
}
if (list.tailType)
{
writer.symbol(",");
visualizeTypePackAnnotation(*list.tailType, false);
}
writer.symbol(")");
}
}
bool isIntegerish(double d)
{
if (d <= std::numeric_limits<int>::max() && d >= std::numeric_limits<int>::min())
return double(int(d)) == d && !(d == 0.0 && signbit(d));
else
return false;
}
void visualize(AstExpr& expr)
{
advance(expr.location.begin);
if (const auto& a = expr.as<AstExprGroup>())
{
writer.symbol("(");
visualize(*a->expr);
writer.symbol(")");
}
else if (expr.is<AstExprConstantNil>())
{
writer.keyword("nil");
}
else if (const auto& a = expr.as<AstExprConstantBool>())
{
if (a->value)
writer.keyword("true");
else
writer.keyword("false");
}
else if (const auto& a = expr.as<AstExprConstantNumber>())
{
if (isinf(a->value))
{
if (a->value > 0)
writer.literal("1e500");
else
writer.literal("-1e500");
}
else if (isnan(a->value))
writer.literal("0/0");
else
{
if (isIntegerish(a->value))
writer.literal(std::to_string(int(a->value)));
else
{
char buffer[100];
size_t len = snprintf(buffer, sizeof(buffer), "%.17g", a->value);
writer.literal(std::string_view{buffer, len});
}
}
}
else if (const auto& a = expr.as<AstExprConstantString>())
{
writer.string(std::string_view(a->value.data, a->value.size));
}
else if (const auto& a = expr.as<AstExprLocal>())
{
writer.identifier(a->local->name.value);
}
else if (const auto& a = expr.as<AstExprGlobal>())
{
writer.identifier(a->name.value);
}
else if (expr.is<AstExprVarargs>())
{
writer.symbol("...");
}
else if (const auto& a = expr.as<AstExprCall>())
{
visualize(*a->func);
writer.symbol("(");
bool first = true;
for (const auto& arg : a->args)
{
if (first)
first = false;
else
writer.symbol(",");
visualize(*arg);
}
writer.symbol(")");
}
else if (const auto& a = expr.as<AstExprIndexName>())
{
visualize(*a->expr);
writer.symbol(std::string(1, a->op));
writer.write(a->index.value);
}
else if (const auto& a = expr.as<AstExprIndexExpr>())
{
visualize(*a->expr);
writer.symbol("[");
visualize(*a->index);
writer.symbol("]");
}
else if (const auto& a = expr.as<AstExprFunction>())
{
writer.keyword("function");
visualizeFunctionBody(*a);
}
else if (const auto& a = expr.as<AstExprTable>())
{
writer.symbol("{");
bool first = true;
for (const auto& item : a->items)
{
if (first)
first = false;
else
writer.symbol(",");
switch (item.kind)
{
case AstExprTable::Item::List:
break;
case AstExprTable::Item::Record:
{
const auto& value = item.key->as<AstExprConstantString>()->value;
advance(item.key->location.begin);
writer.identifier(std::string_view(value.data, value.size));
writer.maybeSpace(item.value->location.begin, 1);
writer.symbol("=");
}
break;
case AstExprTable::Item::General:
{
writer.symbol("[");
visualize(*item.key);
writer.symbol("]");
writer.maybeSpace(item.value->location.begin, 1);
writer.symbol("=");
}
break;
default:
LUAU_ASSERT(!"Unknown table item kind");
}
advance(item.value->location.begin);
visualize(*item.value);
}
// Decrement endPos column so that we advance to before the closing `}` brace before writing, rather than after it
Position endPos = expr.location.end;
if (endPos.column > 0)
--endPos.column;
advance(endPos);
writer.symbol("}");
advance(expr.location.end);
}
else if (const auto& a = expr.as<AstExprUnary>())
{
switch (a->op)
{
case AstExprUnary::Not:
writer.keyword("not");
break;
case AstExprUnary::Minus:
writer.symbol("-");
break;
case AstExprUnary::Len:
writer.symbol("#");
break;
}
visualize(*a->expr);
}
else if (const auto& a = expr.as<AstExprBinary>())
{
visualize(*a->left);
switch (a->op)
{
case AstExprBinary::Add:
case AstExprBinary::Sub:
case AstExprBinary::Mul:
case AstExprBinary::Div:
case AstExprBinary::FloorDiv:
case AstExprBinary::Mod:
case AstExprBinary::Pow:
case AstExprBinary::CompareLt:
case AstExprBinary::CompareGt:
writer.maybeSpace(a->right->location.begin, 2);
writer.symbol(toString(a->op));
break;
case AstExprBinary::Concat:
case AstExprBinary::CompareNe:
case AstExprBinary::CompareEq:
case AstExprBinary::CompareLe:
case AstExprBinary::CompareGe:
case AstExprBinary::Or:
writer.maybeSpace(a->right->location.begin, 3);
writer.keyword(toString(a->op));
break;
case AstExprBinary::And:
writer.maybeSpace(a->right->location.begin, 4);
writer.keyword(toString(a->op));
break;
default:
LUAU_ASSERT(!"Unknown Op");
}
visualize(*a->right);
}
else if (const auto& a = expr.as<AstExprTypeAssertion>())
{
visualize(*a->expr);
if (writeTypes)
{
writer.maybeSpace(a->annotation->location.begin, 2);
writer.symbol("::");
visualizeTypeAnnotation(*a->annotation);
}
}
else if (const auto& a = expr.as<AstExprIfElse>())
{
writer.keyword("if");
visualize(*a->condition);
writer.keyword("then");
visualize(*a->trueExpr);
writer.keyword("else");
visualize(*a->falseExpr);
}
else if (const auto& a = expr.as<AstExprInterpString>())
{
writer.symbol("`");
size_t index = 0;
for (const auto& string : a->strings)
{
writer.write(escape(std::string_view(string.data, string.size), /* escapeForInterpString = */ true));
if (index < a->expressions.size)
{
writer.symbol("{");
visualize(*a->expressions.data[index]);
writer.symbol("}");
}
index++;
}
writer.symbol("`");
}
else if (const auto& a = expr.as<AstExprError>())
{
writer.symbol("(error-expr");
for (size_t i = 0; i < a->expressions.size; i++)
{
writer.symbol(i == 0 ? ": " : ", ");
visualize(*a->expressions.data[i]);
}
writer.symbol(")");
}
else
{
LUAU_ASSERT(!"Unknown AstExpr");
}
}
void writeEnd(const Location& loc)
{
Position endPos = loc.end;
if (endPos.column >= 3)
endPos.column -= 3;
advance(endPos);
writer.keyword("end");
}
void advance(const Position& newPos)
{
writer.advance(newPos);
}
void visualize(AstStat& program)
{
advance(program.location.begin);
if (const auto& block = program.as<AstStatBlock>())
{
writer.keyword("do");
for (const auto& s : block->body)
visualize(*s);
if (!FFlag::LuauFixDoBlockEndLocation)
writer.advance(block->location.end);
writeEnd(program.location);
}
else if (const auto& a = program.as<AstStatIf>())
{
writer.keyword("if");
visualizeElseIf(*a);
}
else if (const auto& a = program.as<AstStatWhile>())
{
writer.keyword("while");
visualize(*a->condition);
writer.keyword("do");
visualizeBlock(*a->body);
writeEnd(program.location);
}
else if (const auto& a = program.as<AstStatRepeat>())
{
writer.keyword("repeat");
visualizeBlock(*a->body);
if (a->condition->location.begin.column > 5)
writer.advance(Position{a->condition->location.begin.line, a->condition->location.begin.column - 6});
writer.keyword("until");
visualize(*a->condition);
}
else if (program.is<AstStatBreak>())
writer.keyword("break");
else if (program.is<AstStatContinue>())
writer.keyword("continue");
else if (const auto& a = program.as<AstStatReturn>())
{
writer.keyword("return");
bool first = true;
for (const auto& expr : a->list)
{
if (first)
first = false;
else
writer.symbol(",");
visualize(*expr);
}
}
else if (const auto& a = program.as<AstStatExpr>())
{
visualize(*a->expr);
}
else if (const auto& a = program.as<AstStatLocal>())
{
writer.keyword("local");
bool first = true;
for (const auto& local : a->vars)
{
if (first)
first = false;
else
writer.write(",");
visualize(*local);
}
first = true;
for (const auto& value : a->values)
{
if (first)
{
first = false;
writer.maybeSpace(value->location.begin, 2);
writer.symbol("=");
}
else
writer.symbol(",");
visualize(*value);
}
}
else if (const auto& a = program.as<AstStatFor>())
{
writer.keyword("for");
visualize(*a->var);
writer.symbol("=");
visualize(*a->from);
writer.symbol(",");
visualize(*a->to);
if (a->step)
{
writer.symbol(",");
visualize(*a->step);
}
writer.keyword("do");
visualizeBlock(*a->body);
writeEnd(program.location);
}
else if (const auto& a = program.as<AstStatForIn>())
{
writer.keyword("for");
bool first = true;
for (const auto& var : a->vars)
{
if (first)
first = false;
else
writer.symbol(",");
visualize(*var);
}
writer.keyword("in");
first = true;
for (const auto& val : a->values)
{
if (first)
first = false;
else
writer.symbol(",");
visualize(*val);
}
writer.keyword("do");
visualizeBlock(*a->body);
writeEnd(program.location);
}
else if (const auto& a = program.as<AstStatAssign>())
{
bool first = true;
for (const auto& var : a->vars)
{
if (first)
first = false;
else
writer.symbol(",");
visualize(*var);
}
first = true;
for (const auto& value : a->values)
{
if (first)
{
writer.maybeSpace(value->location.begin, 1);
writer.symbol("=");
first = false;
}
else
writer.symbol(",");
visualize(*value);
}
}
else if (const auto& a = program.as<AstStatCompoundAssign>())
{
visualize(*a->var);
switch (a->op)
{
case AstExprBinary::Add:
writer.maybeSpace(a->value->location.begin, 2);
writer.symbol("+=");
break;
case AstExprBinary::Sub:
writer.maybeSpace(a->value->location.begin, 2);
writer.symbol("-=");
break;
case AstExprBinary::Mul:
writer.maybeSpace(a->value->location.begin, 2);
writer.symbol("*=");
break;
case AstExprBinary::Div:
writer.maybeSpace(a->value->location.begin, 2);
writer.symbol("/=");
break;
case AstExprBinary::FloorDiv:
writer.maybeSpace(a->value->location.begin, 2);
writer.symbol("//=");
break;
case AstExprBinary::Mod:
writer.maybeSpace(a->value->location.begin, 2);
writer.symbol("%=");
break;
case AstExprBinary::Pow:
writer.maybeSpace(a->value->location.begin, 2);
writer.symbol("^=");
break;
case AstExprBinary::Concat:
writer.maybeSpace(a->value->location.begin, 3);
writer.symbol("..=");
break;
default:
LUAU_ASSERT(!"Unexpected compound assignment op");
}
visualize(*a->value);
}
else if (const auto& a = program.as<AstStatFunction>())
{
writer.keyword("function");
visualize(*a->name);
visualizeFunctionBody(*a->func);
}
else if (const auto& a = program.as<AstStatLocalFunction>())
{
writer.keyword("local function");
advance(a->name->location.begin);
writer.identifier(a->name->name.value);
visualizeFunctionBody(*a->func);
}
else if (const auto& a = program.as<AstStatTypeAlias>())
{
if (writeTypes)
{
if (a->exported)
writer.keyword("export");
writer.keyword("type");
writer.identifier(a->name.value);
if (a->generics.size > 0 || a->genericPacks.size > 0)
{
writer.symbol("<");
CommaSeparatorInserter comma(writer);
for (auto o : a->generics)
{
comma();
writer.advance(o->location.begin);
writer.identifier(o->name.value);
if (o->defaultValue)
{
writer.maybeSpace(o->defaultValue->location.begin, 2);
writer.symbol("=");
visualizeTypeAnnotation(*o->defaultValue);
}
}
for (auto o : a->genericPacks)
{
comma();
writer.advance(o->location.begin);
writer.identifier(o->name.value);
writer.symbol("...");
if (o->defaultValue)
{
writer.maybeSpace(o->defaultValue->location.begin, 2);
writer.symbol("=");
visualizeTypePackAnnotation(*o->defaultValue, false);
}
}
writer.symbol(">");
}
writer.maybeSpace(a->type->location.begin, 2);
writer.symbol("=");
visualizeTypeAnnotation(*a->type);
}
}
else if (const auto& t = program.as<AstStatTypeFunction>())
{
if (writeTypes)
{
writer.keyword("type function");
writer.identifier(t->name.value);
visualizeFunctionBody(*t->body);
}
}
else if (const auto& a = program.as<AstStatError>())
{
writer.symbol("(error-stat");
for (size_t i = 0; i < a->expressions.size; i++)
{
writer.symbol(i == 0 ? ": " : ", ");
visualize(*a->expressions.data[i]);
}
for (size_t i = 0; i < a->statements.size; i++)
{
writer.symbol(i == 0 && a->expressions.size == 0 ? ": " : ", ");
visualize(*a->statements.data[i]);
}
writer.symbol(")");
}
else
{
LUAU_ASSERT(!"Unknown AstStat");
}
if (program.hasSemicolon)
writer.symbol(";");
}
void visualizeFunctionBody(AstExprFunction& func)
{
if (func.generics.size > 0 || func.genericPacks.size > 0)
{
CommaSeparatorInserter comma(writer);
writer.symbol("<");
for (const auto& o : func.generics)
{
comma();
writer.advance(o->location.begin);
writer.identifier(o->name.value);
}
for (const auto& o : func.genericPacks)
{
comma();
writer.advance(o->location.begin);
writer.identifier(o->name.value);
writer.symbol("...");
}
writer.symbol(">");
}
writer.symbol("(");
CommaSeparatorInserter comma(writer);
for (size_t i = 0; i < func.args.size; ++i)
{
AstLocal* local = func.args.data[i];
comma();
advance(local->location.begin);
writer.identifier(local->name.value);
if (writeTypes && local->annotation)
{
writer.symbol(":");
visualizeTypeAnnotation(*local->annotation);
}
}
if (func.vararg)
{
comma();
advance(func.varargLocation.begin);
writer.symbol("...");
if (func.varargAnnotation)
{
writer.symbol(":");
visualizeTypePackAnnotation(*func.varargAnnotation, true);
}
}
writer.symbol(")");
if (writeTypes && func.returnAnnotation)
{
writer.symbol(":");
writer.space();
visualizeTypeList(*func.returnAnnotation, false);
}
visualizeBlock(*func.body);
writeEnd(func.location);
}
void visualizeBlock(AstStatBlock& block)
{
for (const auto& s : block.body)
visualize(*s);
writer.advance(block.location.end);
}
void visualizeBlock(AstStat& stat)
{
if (AstStatBlock* block = stat.as<AstStatBlock>())
visualizeBlock(*block);
else
LUAU_ASSERT(!"visualizeBlock was expecting an AstStatBlock");
}
void visualizeElseIf(AstStatIf& elseif)
{
visualize(*elseif.condition);
writer.keyword("then");
visualizeBlock(*elseif.thenbody);
if (elseif.elsebody == nullptr)
{
writeEnd(elseif.location);
}
else if (auto elseifelseif = elseif.elsebody->as<AstStatIf>())
{
writer.keyword("elseif");
visualizeElseIf(*elseifelseif);
}
else
{
writer.keyword("else");
visualizeBlock(*elseif.elsebody);
writeEnd(elseif.location);
}
}
void visualizeTypeAnnotation(const AstType& typeAnnotation)
{
advance(typeAnnotation.location.begin);
if (const auto& a = typeAnnotation.as<AstTypeReference>())
{
if (a->prefix)
{
writer.write(a->prefix->value);
writer.symbol(".");
}
writer.write(a->name.value);
if (a->parameters.size > 0 || a->hasParameterList)
{
CommaSeparatorInserter comma(writer);
writer.symbol("<");
for (auto o : a->parameters)
{
comma();
if (o.type)
visualizeTypeAnnotation(*o.type);
else
visualizeTypePackAnnotation(*o.typePack, false);
}
writer.symbol(">");
}
}
else if (const auto& a = typeAnnotation.as<AstTypeFunction>())
{
if (a->generics.size > 0 || a->genericPacks.size > 0)
{
CommaSeparatorInserter comma(writer);
writer.symbol("<");
for (const auto& o : a->generics)
{
comma();
writer.advance(o->location.begin);
writer.identifier(o->name.value);
}
for (const auto& o : a->genericPacks)
{
comma();
writer.advance(o->location.begin);
writer.identifier(o->name.value);
writer.symbol("...");
}
writer.symbol(">");
}
{
visualizeTypeList(a->argTypes, true);
}
writer.symbol("->");
visualizeTypeList(a->returnTypes, true);
}
else if (const auto& a = typeAnnotation.as<AstTypeTable>())
{
AstTypeReference* indexType = a->indexer ? a->indexer->indexType->as<AstTypeReference>() : nullptr;
if (a->props.size == 0 && indexType && indexType->name == "number")
{
writer.symbol("{");
visualizeTypeAnnotation(*a->indexer->resultType);
writer.symbol("}");
}
else
{
CommaSeparatorInserter comma(writer);
writer.symbol("{");
for (std::size_t i = 0; i < a->props.size; ++i)
{
comma();
advance(a->props.data[i].location.begin);
writer.identifier(a->props.data[i].name.value);
if (a->props.data[i].type)
{
writer.symbol(":");
visualizeTypeAnnotation(*a->props.data[i].type);
}
}
if (a->indexer)
{
comma();
writer.symbol("[");
visualizeTypeAnnotation(*a->indexer->indexType);
writer.symbol("]");
writer.symbol(":");
visualizeTypeAnnotation(*a->indexer->resultType);
}
writer.symbol("}");
}
}
else if (auto a = typeAnnotation.as<AstTypeTypeof>())
{
writer.keyword("typeof");
writer.symbol("(");
visualize(*a->expr);
writer.symbol(")");
}
else if (const auto& a = typeAnnotation.as<AstTypeUnion>())
{
if (a->types.size == 2)
{
AstType* l = a->types.data[0];
AstType* r = a->types.data[1];
auto lta = l->as<AstTypeReference>();
if (lta && lta->name == "nil")
std::swap(l, r);
// it's still possible that we had a (T | U) or (T | nil) and not (nil | T)
auto rta = r->as<AstTypeReference>();
if (rta && rta->name == "nil")
{
bool wrap = l->as<AstTypeIntersection>() || l->as<AstTypeFunction>();
if (wrap)
writer.symbol("(");
visualizeTypeAnnotation(*l);
if (wrap)
writer.symbol(")");
writer.symbol("?");
return;
}
}
for (size_t i = 0; i < a->types.size; ++i)
{
if (FFlag::LuauParseOptionalAsNode)
{
if (a->types.data[i]->is<AstTypeOptional>())
{
writer.symbol("?");
continue;
}
}
if (i > 0)
{
writer.maybeSpace(a->types.data[i]->location.begin, 2);
writer.symbol("|");
}
bool wrap = a->types.data[i]->as<AstTypeIntersection>() || a->types.data[i]->as<AstTypeFunction>();
if (wrap)
writer.symbol("(");
visualizeTypeAnnotation(*a->types.data[i]);
if (wrap)
writer.symbol(")");
}
}
else if (const auto& a = typeAnnotation.as<AstTypeIntersection>())
{
for (size_t i = 0; i < a->types.size; ++i)
{
if (i > 0)
{
writer.maybeSpace(a->types.data[i]->location.begin, 2);
writer.symbol("&");
}
bool wrap = a->types.data[i]->as<AstTypeUnion>() || a->types.data[i]->as<AstTypeFunction>();
if (wrap)
writer.symbol("(");
visualizeTypeAnnotation(*a->types.data[i]);
if (wrap)
writer.symbol(")");
}
}
else if (const auto& a = typeAnnotation.as<AstTypeGroup>())
{
writer.symbol("(");
visualizeTypeAnnotation(*a->type);
writer.symbol(")");
}
else if (const auto& a = typeAnnotation.as<AstTypeSingletonBool>())
{
writer.keyword(a->value ? "true" : "false");
}
else if (const auto& a = typeAnnotation.as<AstTypeSingletonString>())
{
writer.string(std::string_view(a->value.data, a->value.size));
}
else if (typeAnnotation.is<AstTypeError>())
{
writer.symbol("%error-type%");
}
else
{
LUAU_ASSERT(!"Unknown AstType");
}
}
};
struct Printer
{
explicit Printer(Writer& writer, CstNodeMap cstNodeMap)
: writer(writer)
, cstNodeMap(std::move(cstNodeMap))
{
}
bool writeTypes = false;
Writer& writer;
CstNodeMap cstNodeMap;
template<typename T>
T* lookupCstNode(AstNode* astNode)
{
if (const auto cstNode = cstNodeMap[astNode])
return cstNode->as<T>();
return nullptr;
}
void visualize(const AstLocal& local)
{
advance(local.location.begin);
writer.identifier(local.name.value);
if (writeTypes && local.annotation)
{
// TODO: handle spacing for type annotation
writer.symbol(":");
visualizeTypeAnnotation(*local.annotation);
}
}
void visualizeTypePackAnnotation(AstTypePack& annotation, bool forVarArg)
{
advance(annotation.location.begin);
if (const AstTypePackVariadic* variadicTp = annotation.as<AstTypePackVariadic>())
{
if (!forVarArg)
writer.symbol("...");
visualizeTypeAnnotation(*variadicTp->variadicType);
}
else if (AstTypePackGeneric* genericTp = annotation.as<AstTypePackGeneric>())
{
writer.symbol(genericTp->genericName.value);
if (const auto cstNode = lookupCstNode<CstTypePackGeneric>(genericTp))
advance(cstNode->ellipsisPosition);
writer.symbol("...");
}
else if (AstTypePackExplicit* explicitTp = annotation.as<AstTypePackExplicit>())
{
LUAU_ASSERT(!forVarArg);
if (const auto cstNode = lookupCstNode<CstTypePackExplicit>(explicitTp))
visualizeTypeList(
explicitTp->typeList, true, cstNode->openParenthesesPosition, cstNode->closeParenthesesPosition, cstNode->commaPositions
);
else
visualizeTypeList(explicitTp->typeList, true);
}
else
{
LUAU_ASSERT(!"Unknown TypePackAnnotation kind");
}
}
void visualizeNamedTypeList(
const AstTypeList& list,
bool unconditionallyParenthesize,
std::optional<Position> openParenthesesPosition,
std::optional<Position> closeParenthesesPosition,
AstArray<Position> commaPositions,
AstArray<std::optional<AstArgumentName>> argNames,
AstArray<std::optional<Position>> argNamesColonPositions
)
{
size_t typeCount = list.types.size + (list.tailType != nullptr ? 1 : 0);
if (typeCount == 0)
{
if (openParenthesesPosition)
advance(*openParenthesesPosition);
writer.symbol("(");
if (closeParenthesesPosition)
advance(*closeParenthesesPosition);
writer.symbol(")");
}
else if (typeCount == 1)
{
bool shouldParenthesize = unconditionallyParenthesize && (list.types.size == 0 || !list.types.data[0]->is<AstTypeGroup>());
if (FFlag::LuauAstTypeGroup3 ? shouldParenthesize : unconditionallyParenthesize)
{
if (openParenthesesPosition)
advance(*openParenthesesPosition);
writer.symbol("(");
}
ArgNameInserter(writer, argNames, argNamesColonPositions)();
// Only variadic tail
if (list.types.size == 0)
{
visualizeTypePackAnnotation(*list.tailType, false);
}
else
{
visualizeTypeAnnotation(*list.types.data[0]);
}
if (FFlag::LuauAstTypeGroup3 ? shouldParenthesize : unconditionallyParenthesize)
{
if (closeParenthesesPosition)
advance(*closeParenthesesPosition);
writer.symbol(")");
}
}
else
{
if (openParenthesesPosition)
advance(*openParenthesesPosition);
writer.symbol("(");
CommaSeparatorInserter comma(writer, commaPositions.size > 0 ? commaPositions.begin() : nullptr);
ArgNameInserter argName(writer, argNames, argNamesColonPositions);
for (const auto& el : list.types)
{
comma();
argName();
visualizeTypeAnnotation(*el);
}
if (list.tailType)
{
comma();
visualizeTypePackAnnotation(*list.tailType, false);
}
if (closeParenthesesPosition)
advance(*closeParenthesesPosition);
writer.symbol(")");
}
}
void visualizeTypeList(
const AstTypeList& list,
bool unconditionallyParenthesize,
std::optional<Position> openParenthesesPosition = std::nullopt,
std::optional<Position> closeParenthesesPosition = std::nullopt,
AstArray<Position> commaPositions = {}
)
{
visualizeNamedTypeList(list, unconditionallyParenthesize, openParenthesesPosition, closeParenthesesPosition, commaPositions, {}, {});
}
bool isIntegerish(double d)
{
if (d <= std::numeric_limits<int>::max() && d >= std::numeric_limits<int>::min())
return double(int(d)) == d && !(d == 0.0 && signbit(d));
else
return false;
}
void visualize(AstExpr& expr)
{
advance(expr.location.begin);
if (const auto& a = expr.as<AstExprGroup>())
{
writer.symbol("(");
visualize(*a->expr);
advanceBefore(a->location.end, 1);
writer.symbol(")");
}
else if (expr.is<AstExprConstantNil>())
{
writer.keyword("nil");
}
else if (const auto& a = expr.as<AstExprConstantBool>())
{
if (a->value)
writer.keyword("true");
else
writer.keyword("false");
}
else if (const auto& a = expr.as<AstExprConstantNumber>())
{
if (const auto cstNode = lookupCstNode<CstExprConstantNumber>(a))
{
writer.literal(std::string_view(cstNode->value.data, cstNode->value.size));
}
else
{
if (isinf(a->value))
{
if (a->value > 0)
writer.literal("1e500");
else
writer.literal("-1e500");
}
else if (isnan(a->value))
writer.literal("0/0");
else
{
if (isIntegerish(a->value))
writer.literal(std::to_string(int(a->value)));
else
{
char buffer[100];
size_t len = snprintf(buffer, sizeof(buffer), "%.17g", a->value);
writer.literal(std::string_view{buffer, len});
}
}
}
}
else if (const auto& a = expr.as<AstExprConstantString>())
{
if (const auto cstNode = lookupCstNode<CstExprConstantString>(a))
{
writer.sourceString(
std::string_view(cstNode->sourceString.data, cstNode->sourceString.size), cstNode->quoteStyle, cstNode->blockDepth
);
}
else
writer.string(std::string_view(a->value.data, a->value.size));
}
else if (const auto& a = expr.as<AstExprLocal>())
{
writer.identifier(a->local->name.value);
}
else if (const auto& a = expr.as<AstExprGlobal>())
{
writer.identifier(a->name.value);
}
else if (expr.is<AstExprVarargs>())
{
writer.symbol("...");
}
else if (const auto& a = expr.as<AstExprCall>())
{
visualize(*a->func);
const auto cstNode = lookupCstNode<CstExprCall>(a);
if (cstNode)
{
if (cstNode->openParens)
{
advance(*cstNode->openParens);
writer.symbol("(");
}
}
else
{
writer.symbol("(");
}
CommaSeparatorInserter comma(writer, cstNode ? cstNode->commaPositions.begin() : nullptr);
for (const auto& arg : a->args)
{
comma();
visualize(*arg);
}
if (cstNode)
{
if (cstNode->closeParens)
{
advance(*cstNode->closeParens);
writer.symbol(")");
}
}
else
{
writer.symbol(")");
}
}
else if (const auto& a = expr.as<AstExprIndexName>())
{
visualize(*a->expr);
advance(a->opPosition);
writer.symbol(std::string(1, a->op));
advance(a->indexLocation.begin);
writer.write(a->index.value);
}
else if (const auto& a = expr.as<AstExprIndexExpr>())
{
const auto cstNode = lookupCstNode<CstExprIndexExpr>(a);
visualize(*a->expr);
if (cstNode)
advance(cstNode->openBracketPosition);
writer.symbol("[");
visualize(*a->index);
if (cstNode)
advance(cstNode->closeBracketPosition);
writer.symbol("]");
}
else if (const auto& a = expr.as<AstExprFunction>())
{
writer.keyword("function");
visualizeFunctionBody(*a);
}
else if (const auto& a = expr.as<AstExprTable>())
{
writer.symbol("{");
const CstExprTable::Item* cstItem = nullptr;
if (const auto cstNode = lookupCstNode<CstExprTable>(a))
{
LUAU_ASSERT(cstNode->items.size == a->items.size);
cstItem = cstNode->items.begin();
}
bool first = true;
for (const auto& item : a->items)
{
if (!cstItem)
{
if (first)
first = false;
else
writer.symbol(",");
}
switch (item.kind)
{
case AstExprTable::Item::List:
break;
case AstExprTable::Item::Record:
{
const auto& value = item.key->as<AstExprConstantString>()->value;
advance(item.key->location.begin);
writer.identifier(std::string_view(value.data, value.size));
if (cstItem)
advance(*cstItem->equalsPosition);
else
writer.maybeSpace(item.value->location.begin, 1);
writer.symbol("=");
}
break;
case AstExprTable::Item::General:
{
if (cstItem)
advance(*cstItem->indexerOpenPosition);
writer.symbol("[");
visualize(*item.key);
if (cstItem)
advance(*cstItem->indexerClosePosition);
writer.symbol("]");
if (cstItem)
advance(*cstItem->equalsPosition);
else
writer.maybeSpace(item.value->location.begin, 1);
writer.symbol("=");
}
break;
default:
LUAU_ASSERT(!"Unknown table item kind");
}
advance(item.value->location.begin);
visualize(*item.value);
if (cstItem)
{
if (cstItem->separator)
{
LUAU_ASSERT(cstItem->separatorPosition);
advance(*cstItem->separatorPosition);
if (cstItem->separator == CstExprTable::Comma)
writer.symbol(",");
else if (cstItem->separator == CstExprTable::Semicolon)
writer.symbol(";");
}
cstItem++;
}
}
Position endPos = expr.location.end;
if (endPos.column > 0)
--endPos.column;
advance(endPos);
writer.symbol("}");
advance(expr.location.end);
}
else if (const auto& a = expr.as<AstExprUnary>())
{
if (const auto cstNode = lookupCstNode<CstExprOp>(a))
advance(cstNode->opPosition);
switch (a->op)
{
case AstExprUnary::Not:
writer.keyword("not");
break;
case AstExprUnary::Minus:
writer.symbol("-");
break;
case AstExprUnary::Len:
writer.symbol("#");
break;
}
visualize(*a->expr);
}
else if (const auto& a = expr.as<AstExprBinary>())
{
visualize(*a->left);
if (const auto cstNode = lookupCstNode<CstExprOp>(a))
advance(cstNode->opPosition);
else
{
switch (a->op)
{
case AstExprBinary::Add:
case AstExprBinary::Sub:
case AstExprBinary::Mul:
case AstExprBinary::Div:
case AstExprBinary::FloorDiv:
case AstExprBinary::Mod:
case AstExprBinary::Pow:
case AstExprBinary::CompareLt:
case AstExprBinary::CompareGt:
writer.maybeSpace(a->right->location.begin, 2);
break;
case AstExprBinary::Concat:
case AstExprBinary::CompareNe:
case AstExprBinary::CompareEq:
case AstExprBinary::CompareLe:
case AstExprBinary::CompareGe:
case AstExprBinary::Or:
writer.maybeSpace(a->right->location.begin, 3);
break;
case AstExprBinary::And:
writer.maybeSpace(a->right->location.begin, 4);
break;
default:
LUAU_ASSERT(!"Unknown Op");
}
}
writer.symbol(toString(a->op));
visualize(*a->right);
}
else if (const auto& a = expr.as<AstExprTypeAssertion>())
{
visualize(*a->expr);
if (writeTypes)
{
if (const auto* cstNode = lookupCstNode<CstExprTypeAssertion>(a))
advance(cstNode->opPosition);
else
writer.maybeSpace(a->annotation->location.begin, 2);
writer.symbol("::");
visualizeTypeAnnotation(*a->annotation);
}
}
else if (const auto& a = expr.as<AstExprIfElse>())
{
writer.keyword("if");
visualizeElseIfExpr(*a);
}
else if (const auto& a = expr.as<AstExprInterpString>())
{
const auto* cstNode = lookupCstNode<CstExprInterpString>(a);
writer.symbol("`");
size_t index = 0;
for (const auto& string : a->strings)
{
if (cstNode)
{
if (index > 0)
{
advance(cstNode->stringPositions.data[index]);
writer.symbol("}");
}
const AstArray<char> sourceString = cstNode->sourceStrings.data[index];
writer.writeMultiline(std::string_view(sourceString.data, sourceString.size));
}
else
{
writer.write(escape(std::string_view(string.data, string.size), /* escapeForInterpString = */ true));
}
if (index < a->expressions.size)
{
writer.symbol("{");
visualize(*a->expressions.data[index]);
if (!cstNode)
writer.symbol("}");
}
index++;
}
writer.symbol("`");
}
else if (const auto& a = expr.as<AstExprError>())
{
writer.symbol("(error-expr");
for (size_t i = 0; i < a->expressions.size; i++)
{
writer.symbol(i == 0 ? ": " : ", ");
visualize(*a->expressions.data[i]);
}
writer.symbol(")");
}
else
{
LUAU_ASSERT(!"Unknown AstExpr");
}
}
void writeEnd(const Location& loc)
{
Position endPos = loc.end;
if (endPos.column >= 3)
endPos.column -= 3;
advance(endPos);
writer.keyword("end");
}
void advance(const Position& newPos)
{
writer.advance(newPos);
}
void advanceBefore(const Position& newPos, unsigned int tokenLength)
{
if (newPos.column >= tokenLength)
advance(Position{newPos.line, newPos.column - tokenLength});
else
advance(newPos);
}
void visualize(AstStat& program)
{
advance(program.location.begin);
if (const auto& block = program.as<AstStatBlock>())
{
writer.keyword("do");
for (const auto& s : block->body)
visualize(*s);
if (const auto cstNode = lookupCstNode<CstStatDo>(block))
{
advance(cstNode->endPosition);
writer.keyword("end");
}
else
{
writer.advance(block->location.end);
writeEnd(program.location);
}
}
else if (const auto& a = program.as<AstStatIf>())
{
writer.keyword("if");
visualizeElseIf(*a);
}
else if (const auto& a = program.as<AstStatWhile>())
{
writer.keyword("while");
visualize(*a->condition);
// TODO: what if 'hasDo = false'?
advance(a->doLocation.begin);
writer.keyword("do");
visualizeBlock(*a->body);
advance(a->body->location.end);
writer.keyword("end");
}
else if (const auto& a = program.as<AstStatRepeat>())
{
writer.keyword("repeat");
visualizeBlock(*a->body);
if (const auto cstNode = lookupCstNode<CstStatRepeat>(a))
writer.advance(cstNode->untilPosition);
else
advanceBefore(a->condition->location.begin, 6);
writer.keyword("until");
visualize(*a->condition);
}
else if (program.is<AstStatBreak>())
writer.keyword("break");
else if (program.is<AstStatContinue>())
writer.keyword("continue");
else if (const auto& a = program.as<AstStatReturn>())
{
const auto cstNode = lookupCstNode<CstStatReturn>(a);
writer.keyword("return");
CommaSeparatorInserter comma(writer, cstNode ? cstNode->commaPositions.begin() : nullptr);
for (const auto& expr : a->list)
{
comma();
visualize(*expr);
}
}
else if (const auto& a = program.as<AstStatExpr>())
{
visualize(*a->expr);
}
else if (const auto& a = program.as<AstStatLocal>())
{
const auto cstNode = lookupCstNode<CstStatLocal>(a);
writer.keyword("local");
CommaSeparatorInserter varComma(writer, cstNode ? cstNode->varsCommaPositions.begin() : nullptr);
for (const auto& local : a->vars)
{
varComma();
visualize(*local);
}
if (a->equalsSignLocation)
{
advance(a->equalsSignLocation->begin);
writer.symbol("=");
}
CommaSeparatorInserter valueComma(writer, cstNode ? cstNode->valuesCommaPositions.begin() : nullptr);
for (const auto& value : a->values)
{
valueComma();
visualize(*value);
}
}
else if (const auto& a = program.as<AstStatFor>())
{
const auto cstNode = lookupCstNode<CstStatFor>(a);
writer.keyword("for");
visualize(*a->var);
if (cstNode)
advance(cstNode->equalsPosition);
writer.symbol("=");
visualize(*a->from);
if (cstNode)
advance(cstNode->endCommaPosition);
writer.symbol(",");
visualize(*a->to);
if (a->step)
{
if (cstNode && cstNode->stepCommaPosition)
advance(*cstNode->stepCommaPosition);
writer.symbol(",");
visualize(*a->step);
}
advance(a->doLocation.begin);
writer.keyword("do");
visualizeBlock(*a->body);
advance(a->body->location.end);
writer.keyword("end");
}
else if (const auto& a = program.as<AstStatForIn>())
{
const auto cstNode = lookupCstNode<CstStatForIn>(a);
writer.keyword("for");
CommaSeparatorInserter varComma(writer, cstNode ? cstNode->varsCommaPositions.begin() : nullptr);
for (const auto& var : a->vars)
{
varComma();
visualize(*var);
}
advance(a->inLocation.begin);
writer.keyword("in");
CommaSeparatorInserter valComma(writer, cstNode ? cstNode->valuesCommaPositions.begin() : nullptr);
for (const auto& val : a->values)
{
valComma();
visualize(*val);
}
advance(a->doLocation.begin);
writer.keyword("do");
visualizeBlock(*a->body);
advance(a->body->location.end);
writer.keyword("end");
}
else if (const auto& a = program.as<AstStatAssign>())
{
const auto cstNode = lookupCstNode<CstStatAssign>(a);
CommaSeparatorInserter varComma(writer, cstNode ? cstNode->varsCommaPositions.begin() : nullptr);
for (const auto& var : a->vars)
{
varComma();
visualize(*var);
}
if (cstNode)
advance(cstNode->equalsPosition);
else
writer.space();
writer.symbol("=");
CommaSeparatorInserter valueComma(writer, cstNode ? cstNode->valuesCommaPositions.begin() : nullptr);
for (const auto& value : a->values)
{
valueComma();
visualize(*value);
}
}
else if (const auto& a = program.as<AstStatCompoundAssign>())
{
const auto cstNode = lookupCstNode<CstStatCompoundAssign>(a);
visualize(*a->var);
if (cstNode)
advance(cstNode->opPosition);
switch (a->op)
{
case AstExprBinary::Add:
if (!cstNode)
writer.maybeSpace(a->value->location.begin, 2);
writer.symbol("+=");
break;
case AstExprBinary::Sub:
if (!cstNode)
writer.maybeSpace(a->value->location.begin, 2);
writer.symbol("-=");
break;
case AstExprBinary::Mul:
if (!cstNode)
writer.maybeSpace(a->value->location.begin, 2);
writer.symbol("*=");
break;
case AstExprBinary::Div:
if (!cstNode)
writer.maybeSpace(a->value->location.begin, 2);
writer.symbol("/=");
break;
case AstExprBinary::FloorDiv:
if (!cstNode)
writer.maybeSpace(a->value->location.begin, 3);
writer.symbol("//=");
break;
case AstExprBinary::Mod:
if (!cstNode)
writer.maybeSpace(a->value->location.begin, 2);
writer.symbol("%=");
break;
case AstExprBinary::Pow:
if (!cstNode)
writer.maybeSpace(a->value->location.begin, 2);
writer.symbol("^=");
break;
case AstExprBinary::Concat:
if (!cstNode)
writer.maybeSpace(a->value->location.begin, 3);
writer.symbol("..=");
break;
default:
LUAU_ASSERT(!"Unexpected compound assignment op");
}
visualize(*a->value);
}
else if (const auto& a = program.as<AstStatFunction>())
{
writer.keyword("function");
visualize(*a->name);
visualizeFunctionBody(*a->func);
}
else if (const auto& a = program.as<AstStatLocalFunction>())
{
const auto cstNode = lookupCstNode<CstStatLocalFunction>(a);
writer.keyword("local");
if (cstNode)
advance(cstNode->functionKeywordPosition);
else
writer.space();
writer.keyword("function");
advance(a->name->location.begin);
writer.identifier(a->name->name.value);
visualizeFunctionBody(*a->func);
}
else if (const auto& a = program.as<AstStatTypeAlias>())
{
if (writeTypes)
{
const auto* cstNode = lookupCstNode<CstStatTypeAlias>(a);
if (a->exported)
writer.keyword("export");
if (cstNode)
advance(cstNode->typeKeywordPosition);
writer.keyword("type");
advance(a->nameLocation.begin);
writer.identifier(a->name.value);
if (a->generics.size > 0 || a->genericPacks.size > 0)
{
if (cstNode)
advance(cstNode->genericsOpenPosition);
writer.symbol("<");
CommaSeparatorInserter comma(writer, cstNode ? cstNode->genericsCommaPositions.begin() : nullptr);
for (auto o : a->generics)
{
comma();
writer.advance(o->location.begin);
writer.identifier(o->name.value);
if (o->defaultValue)
{
const auto* genericTypeCstNode = lookupCstNode<CstGenericType>(o);
if (genericTypeCstNode)
{
LUAU_ASSERT(genericTypeCstNode->defaultEqualsPosition.has_value());
advance(*genericTypeCstNode->defaultEqualsPosition);
}
else
writer.maybeSpace(o->defaultValue->location.begin, 2);
writer.symbol("=");
visualizeTypeAnnotation(*o->defaultValue);
}
}
for (auto o : a->genericPacks)
{
comma();
const auto* genericTypePackCstNode = lookupCstNode<CstGenericTypePack>(o);
writer.advance(o->location.begin);
writer.identifier(o->name.value);
if (genericTypePackCstNode)
advance(genericTypePackCstNode->ellipsisPosition);
writer.symbol("...");
if (o->defaultValue)
{
if (cstNode)
{
LUAU_ASSERT(genericTypePackCstNode->defaultEqualsPosition.has_value());
advance(*genericTypePackCstNode->defaultEqualsPosition);
}
else
writer.maybeSpace(o->defaultValue->location.begin, 2);
writer.symbol("=");
visualizeTypePackAnnotation(*o->defaultValue, false);
}
}
if (cstNode)
advance(cstNode->genericsClosePosition);
writer.symbol(">");
}
if (cstNode)
advance(cstNode->equalsPosition);
else
writer.maybeSpace(a->type->location.begin, 2);
writer.symbol("=");
visualizeTypeAnnotation(*a->type);
}
}
else if (const auto& t = program.as<AstStatTypeFunction>())
{
if (writeTypes)
{
const auto cstNode = lookupCstNode<CstStatTypeFunction>(t);
if (t->exported)
writer.keyword("export");
if (cstNode)
advance(cstNode->typeKeywordPosition);
else
writer.space();
writer.keyword("type");
if (cstNode)
advance(cstNode->functionKeywordPosition);
else
writer.space();
writer.keyword("function");
advance(t->nameLocation.begin);
writer.identifier(t->name.value);
visualizeFunctionBody(*t->body);
}
}
else if (const auto& a = program.as<AstStatError>())
{
writer.symbol("(error-stat");
for (size_t i = 0; i < a->expressions.size; i++)
{
writer.symbol(i == 0 ? ": " : ", ");
visualize(*a->expressions.data[i]);
}
for (size_t i = 0; i < a->statements.size; i++)
{
writer.symbol(i == 0 && a->expressions.size == 0 ? ": " : ", ");
visualize(*a->statements.data[i]);
}
writer.symbol(")");
}
else
{
LUAU_ASSERT(!"Unknown AstStat");
}
if (program.hasSemicolon)
{
if (FFlag::LuauStoreCSTData)
advanceBefore(program.location.end, 1);
writer.symbol(";");
}
}
void visualizeFunctionBody(AstExprFunction& func)
{
const auto cstNode = lookupCstNode<CstExprFunction>(&func);
// TODO(CLI-139347): need to handle attributes, argument types, and return type (incl. parentheses of return type)
if (func.generics.size > 0 || func.genericPacks.size > 0)
{
CommaSeparatorInserter comma(writer, cstNode ? cstNode->genericsCommaPositions.begin() : nullptr);
if (cstNode)
advance(cstNode->openGenericsPosition);
writer.symbol("<");
for (const auto& o : func.generics)
{
comma();
writer.advance(o->location.begin);
writer.identifier(o->name.value);
}
for (const auto& o : func.genericPacks)
{
comma();
writer.advance(o->location.begin);
writer.identifier(o->name.value);
if (const auto* genericTypePackCstNode = lookupCstNode<CstGenericTypePack>(o))
advance(genericTypePackCstNode->ellipsisPosition);
writer.symbol("...");
}
if (cstNode)
advance(cstNode->closeGenericsPosition);
writer.symbol(">");
}
if (func.argLocation)
advance(func.argLocation->begin);
writer.symbol("(");
CommaSeparatorInserter comma(writer, cstNode ? cstNode->argsCommaPositions.begin() : nullptr);
for (size_t i = 0; i < func.args.size; ++i)
{
AstLocal* local = func.args.data[i];
comma();
advance(local->location.begin);
writer.identifier(local->name.value);
if (writeTypes && local->annotation)
{
writer.symbol(":");
visualizeTypeAnnotation(*local->annotation);
}
}
if (func.vararg)
{
comma();
advance(func.varargLocation.begin);
writer.symbol("...");
if (func.varargAnnotation)
{
writer.symbol(":");
visualizeTypePackAnnotation(*func.varargAnnotation, true);
}
}
if (func.argLocation)
advanceBefore(func.argLocation->end, 1);
writer.symbol(")");
if (writeTypes && func.returnAnnotation)
{
if (cstNode)
advance(cstNode->returnSpecifierPosition);
writer.symbol(":");
writer.space();
visualizeTypeList(*func.returnAnnotation, false);
}
visualizeBlock(*func.body);
advance(func.body->location.end);
writer.keyword("end");
}
void visualizeBlock(AstStatBlock& block)
{
for (const auto& s : block.body)
visualize(*s);
writer.advance(block.location.end);
}
void visualizeBlock(AstStat& stat)
{
if (AstStatBlock* block = stat.as<AstStatBlock>())
visualizeBlock(*block);
else
LUAU_ASSERT(!"visualizeBlock was expecting an AstStatBlock");
}
void visualizeElseIf(AstStatIf& elseif)
{
visualize(*elseif.condition);
if (elseif.thenLocation)
advance(elseif.thenLocation->begin);
writer.keyword("then");
visualizeBlock(*elseif.thenbody);
if (elseif.elsebody == nullptr)
{
advance(elseif.thenbody->location.end);
writer.keyword("end");
}
else if (auto elseifelseif = elseif.elsebody->as<AstStatIf>())
{
if (elseif.elseLocation)
advance(elseif.elseLocation->begin);
writer.keyword("elseif");
visualizeElseIf(*elseifelseif);
}
else
{
if (elseif.elseLocation)
advance(elseif.elseLocation->begin);
writer.keyword("else");
visualizeBlock(*elseif.elsebody);
advance(elseif.elsebody->location.end);
writer.keyword("end");
}
}
void visualizeElseIfExpr(AstExprIfElse& elseif)
{
const auto cstNode = lookupCstNode<CstExprIfElse>(&elseif);
visualize(*elseif.condition);
if (cstNode)
advance(cstNode->thenPosition);
writer.keyword("then");
visualize(*elseif.trueExpr);
if (elseif.falseExpr)
{
if (cstNode)
advance(cstNode->elsePosition);
if (auto elseifelseif = elseif.falseExpr->as<AstExprIfElse>(); elseifelseif && (!cstNode || cstNode->isElseIf))
{
writer.keyword("elseif");
visualizeElseIfExpr(*elseifelseif);
}
else
{
writer.keyword("else");
visualize(*elseif.falseExpr);
}
}
}
void visualizeTypeAnnotation(AstType& typeAnnotation)
{
advance(typeAnnotation.location.begin);
if (const auto& a = typeAnnotation.as<AstTypeReference>())
{
const auto cstNode = lookupCstNode<CstTypeReference>(a);
if (a->prefix)
{
writer.write(a->prefix->value);
if (cstNode)
advance(*cstNode->prefixPointPosition);
writer.symbol(".");
}
advance(a->nameLocation.begin);
writer.write(a->name.value);
if (a->parameters.size > 0 || a->hasParameterList)
{
CommaSeparatorInserter comma(writer, cstNode ? cstNode->parametersCommaPositions.begin() : nullptr);
if (cstNode)
advance(cstNode->openParametersPosition);
writer.symbol("<");
for (auto o : a->parameters)
{
comma();
if (o.type)
visualizeTypeAnnotation(*o.type);
else
visualizeTypePackAnnotation(*o.typePack, false);
}
if (cstNode)
advance(cstNode->closeParametersPosition);
writer.symbol(">");
}
}
else if (const auto& a = typeAnnotation.as<AstTypeFunction>())
{
const auto cstNode = lookupCstNode<CstTypeFunction>(a);
if (a->generics.size > 0 || a->genericPacks.size > 0)
{
CommaSeparatorInserter comma(writer, cstNode ? cstNode->genericsCommaPositions.begin() : nullptr);
if (cstNode)
advance(cstNode->openGenericsPosition);
writer.symbol("<");
for (const auto& o : a->generics)
{
comma();
writer.advance(o->location.begin);
writer.identifier(o->name.value);
}
for (const auto& o : a->genericPacks)
{
comma();
writer.advance(o->location.begin);
writer.identifier(o->name.value);
if (const auto* genericTypePackCstNode = lookupCstNode<CstGenericTypePack>(o))
advance(genericTypePackCstNode->ellipsisPosition);
writer.symbol("...");
}
if (cstNode)
advance(cstNode->closeGenericsPosition);
writer.symbol(">");
}
{
visualizeNamedTypeList(
a->argTypes,
true,
cstNode ? std::make_optional(cstNode->openArgsPosition) : std::nullopt,
cstNode ? std::make_optional(cstNode->closeArgsPosition) : std::nullopt,
cstNode ? cstNode->argumentsCommaPositions : Luau::AstArray<Position>{},
a->argNames,
cstNode ? cstNode->argumentNameColonPositions : Luau::AstArray<std::optional<Position>>{}
);
}
if (cstNode)
advance(cstNode->returnArrowPosition);
writer.symbol("->");
visualizeTypeList(a->returnTypes, true);
}
else if (const auto& a = typeAnnotation.as<AstTypeTable>())
{
AstTypeReference* indexType = a->indexer ? a->indexer->indexType->as<AstTypeReference>() : nullptr;
writer.symbol("{");
const auto cstNode = lookupCstNode<CstTypeTable>(a);
if (cstNode)
{
if (cstNode->isArray)
{
LUAU_ASSERT(a->props.size == 0 && indexType && indexType->name == "number");
if (a->indexer->accessLocation)
{
LUAU_ASSERT(a->indexer->access != AstTableAccess::ReadWrite);
advance(a->indexer->accessLocation->begin);
writer.keyword(a->indexer->access == AstTableAccess::Read ? "read" : "write");
}
visualizeTypeAnnotation(*a->indexer->resultType);
}
else
{
const AstTableProp* prop = a->props.begin();
for (size_t i = 0; i < cstNode->items.size; ++i)
{
CstTypeTable::Item item = cstNode->items.data[i];
// we store indexer as part of items to preserve property ordering
if (item.kind == CstTypeTable::Item::Kind::Indexer)
{
LUAU_ASSERT(a->indexer);
if (a->indexer->accessLocation)
{
LUAU_ASSERT(a->indexer->access != AstTableAccess::ReadWrite);
advance(a->indexer->accessLocation->begin);
writer.keyword(a->indexer->access == AstTableAccess::Read ? "read" : "write");
}
advance(item.indexerOpenPosition);
writer.symbol("[");
visualizeTypeAnnotation(*a->indexer->indexType);
advance(item.indexerClosePosition);
writer.symbol("]");
advance(item.colonPosition);
writer.symbol(":");
visualizeTypeAnnotation(*a->indexer->resultType);
if (item.separator)
{
LUAU_ASSERT(item.separatorPosition);
advance(*item.separatorPosition);
if (item.separator == CstExprTable::Comma)
writer.symbol(",");
else if (item.separator == CstExprTable::Semicolon)
writer.symbol(";");
}
}
else
{
if (prop->accessLocation)
{
LUAU_ASSERT(prop->access != AstTableAccess::ReadWrite);
advance(prop->accessLocation->begin);
writer.keyword(prop->access == AstTableAccess::Read ? "read" : "write");
}
if (item.kind == CstTypeTable::Item::Kind::StringProperty)
{
advance(item.indexerOpenPosition);
writer.symbol("[");
writer.sourceString(
std::string_view(item.stringInfo->sourceString.data, item.stringInfo->sourceString.size),
item.stringInfo->quoteStyle,
item.stringInfo->blockDepth
);
advance(item.indexerClosePosition);
writer.symbol("]");
}
else
{
advance(prop->location.begin);
writer.identifier(prop->name.value);
}
advance(item.colonPosition);
writer.symbol(":");
visualizeTypeAnnotation(*prop->type);
if (item.separator)
{
LUAU_ASSERT(item.separatorPosition);
advance(*item.separatorPosition);
if (item.separator == CstExprTable::Comma)
writer.symbol(",");
else if (item.separator == CstExprTable::Semicolon)
writer.symbol(";");
}
++prop;
}
}
}
}
else
{
if (a->props.size == 0 && indexType && indexType->name == "number")
{
visualizeTypeAnnotation(*a->indexer->resultType);
}
else
{
CommaSeparatorInserter comma(writer);
for (size_t i = 0; i < a->props.size; ++i)
{
comma();
advance(a->props.data[i].location.begin);
writer.identifier(a->props.data[i].name.value);
if (a->props.data[i].type)
{
writer.symbol(":");
visualizeTypeAnnotation(*a->props.data[i].type);
}
}
if (a->indexer)
{
comma();
writer.symbol("[");
visualizeTypeAnnotation(*a->indexer->indexType);
writer.symbol("]");
writer.symbol(":");
visualizeTypeAnnotation(*a->indexer->resultType);
}
}
}
Position endPos = a->location.end;
if (endPos.column > 0)
--endPos.column;
advance(endPos);
writer.symbol("}");
}
else if (auto a = typeAnnotation.as<AstTypeTypeof>())
{
const auto cstNode = lookupCstNode<CstTypeTypeof>(a);
writer.keyword("typeof");
if (cstNode)
advance(cstNode->openPosition);
writer.symbol("(");
visualize(*a->expr);
if (cstNode)
advance(cstNode->closePosition);
writer.symbol(")");
}
else if (const auto& a = typeAnnotation.as<AstTypeUnion>())
{
if (a->types.size == 2)
{
AstType* l = a->types.data[0];
AstType* r = a->types.data[1];
auto lta = l->as<AstTypeReference>();
if (lta && lta->name == "nil")
std::swap(l, r);
// it's still possible that we had a (T | U) or (T | nil) and not (nil | T)
auto rta = r->as<AstTypeReference>();
if (rta && rta->name == "nil")
{
bool wrap = l->as<AstTypeIntersection>() || l->as<AstTypeFunction>();
if (wrap)
writer.symbol("(");
visualizeTypeAnnotation(*l);
if (wrap)
writer.symbol(")");
writer.symbol("?");
return;
}
}
for (size_t i = 0; i < a->types.size; ++i)
{
if (FFlag::LuauParseOptionalAsNode)
{
if (a->types.data[i]->is<AstTypeOptional>())
{
writer.symbol("?");
continue;
}
}
if (i > 0)
{
writer.maybeSpace(a->types.data[i]->location.begin, 2);
writer.symbol("|");
}
bool wrap = a->types.data[i]->as<AstTypeIntersection>() || a->types.data[i]->as<AstTypeFunction>();
if (wrap)
writer.symbol("(");
visualizeTypeAnnotation(*a->types.data[i]);
if (wrap)
writer.symbol(")");
}
}
else if (const auto& a = typeAnnotation.as<AstTypeIntersection>())
{
for (size_t i = 0; i < a->types.size; ++i)
{
if (i > 0)
{
writer.maybeSpace(a->types.data[i]->location.begin, 2);
writer.symbol("&");
}
bool wrap = a->types.data[i]->as<AstTypeUnion>() || a->types.data[i]->as<AstTypeFunction>();
if (wrap)
writer.symbol("(");
visualizeTypeAnnotation(*a->types.data[i]);
if (wrap)
writer.symbol(")");
}
}
else if (const auto& a = typeAnnotation.as<AstTypeGroup>())
{
writer.symbol("(");
visualizeTypeAnnotation(*a->type);
advanceBefore(a->location.end, 1);
writer.symbol(")");
}
else if (const auto& a = typeAnnotation.as<AstTypeSingletonBool>())
{
writer.keyword(a->value ? "true" : "false");
}
else if (const auto& a = typeAnnotation.as<AstTypeSingletonString>())
{
if (const auto cstNode = lookupCstNode<CstTypeSingletonString>(a))
{
writer.sourceString(
std::string_view(cstNode->sourceString.data, cstNode->sourceString.size), cstNode->quoteStyle, cstNode->blockDepth
);
}
else
writer.string(std::string_view(a->value.data, a->value.size));
}
else if (typeAnnotation.is<AstTypeError>())
{
writer.symbol("%error-type%");
}
else
{
LUAU_ASSERT(!"Unknown AstType");
}
}
};
std::string toString(AstNode* node)
{
StringWriter writer;
writer.pos = node->location.begin;
if (FFlag::LuauStoreCSTData)
{
Printer printer(writer, CstNodeMap{nullptr});
printer.writeTypes = true;
if (auto statNode = node->asStat())
printer.visualize(*statNode);
else if (auto exprNode = node->asExpr())
printer.visualize(*exprNode);
else if (auto typeNode = node->asType())
printer.visualizeTypeAnnotation(*typeNode);
}
else
{
Printer_DEPRECATED printer(writer);
printer.writeTypes = true;
if (auto statNode = node->asStat())
printer.visualize(*statNode);
else if (auto exprNode = node->asExpr())
printer.visualize(*exprNode);
else if (auto typeNode = node->asType())
printer.visualizeTypeAnnotation(*typeNode);
}
return writer.str();
}
void dump(AstNode* node)
{
printf("%s\n", toString(node).c_str());
}
std::string transpile(AstStatBlock& block, const CstNodeMap& cstNodeMap)
{
StringWriter writer;
if (FFlag::LuauStoreCSTData)
{
Printer(writer, cstNodeMap).visualizeBlock(block);
}
else
{
Printer_DEPRECATED(writer).visualizeBlock(block);
}
return writer.str();
}
std::string transpileWithTypes(AstStatBlock& block, const CstNodeMap& cstNodeMap)
{
StringWriter writer;
if (FFlag::LuauStoreCSTData)
{
Printer printer(writer, cstNodeMap);
printer.writeTypes = true;
printer.visualizeBlock(block);
}
else
{
Printer_DEPRECATED printer(writer);
printer.writeTypes = true;
printer.visualizeBlock(block);
}
return writer.str();
}
std::string transpileWithTypes(AstStatBlock& block)
{
// TODO: remove this interface?
return transpileWithTypes(block, CstNodeMap{nullptr});
}
TranspileResult transpile(std::string_view source, ParseOptions options, bool withTypes)
{
options.storeCstData = true;
auto allocator = Allocator{};
auto names = AstNameTable{allocator};
ParseResult parseResult = Parser::parse(source.data(), source.size(), names, allocator, options);
if (!parseResult.errors.empty())
{
// TranspileResult keeps track of only a single error
const ParseError& error = parseResult.errors.front();
return TranspileResult{"", error.getLocation(), error.what()};
}
LUAU_ASSERT(parseResult.root);
if (!parseResult.root)
return TranspileResult{"", {}, "Internal error: Parser yielded empty parse tree"};
if (withTypes)
return TranspileResult{transpileWithTypes(*parseResult.root, parseResult.cstNodeMap)};
return TranspileResult{transpile(*parseResult.root, parseResult.cstNodeMap)};
}
} // namespace Luau