// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #include "Luau/DataFlowGraph.h" #include "Luau/Ast.h" #include "Luau/Def.h" #include "Luau/Common.h" #include "Luau/Error.h" #include #include LUAU_FASTFLAG(DebugLuauFreezeArena) LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution) LUAU_FASTFLAG(LuauLoopControlFlowAnalysis) namespace Luau { bool doesCallError(const AstExprCall* call); // TypeInfer.cpp const RefinementKey* RefinementKeyArena::leaf(DefId def) { return allocator.allocate(RefinementKey{nullptr, def, std::nullopt}); } const RefinementKey* RefinementKeyArena::node(const RefinementKey* parent, DefId def, const std::string& propName) { return allocator.allocate(RefinementKey{parent, def, propName}); } DefId DataFlowGraph::getDef(const AstExpr* expr) const { auto def = astDefs.find(expr); LUAU_ASSERT(def); return NotNull{*def}; } std::optional DataFlowGraph::getRValueDefForCompoundAssign(const AstExpr* expr) const { auto def = compoundAssignDefs.find(expr); return def ? std::optional(*def) : std::nullopt; } DefId DataFlowGraph::getDef(const AstLocal* local) const { auto def = localDefs.find(local); LUAU_ASSERT(def); return NotNull{*def}; } DefId DataFlowGraph::getDef(const AstStatDeclareGlobal* global) const { auto def = declaredDefs.find(global); LUAU_ASSERT(def); return NotNull{*def}; } DefId DataFlowGraph::getDef(const AstStatDeclareFunction* func) const { auto def = declaredDefs.find(func); LUAU_ASSERT(def); return NotNull{*def}; } const RefinementKey* DataFlowGraph::getRefinementKey(const AstExpr* expr) const { if (auto key = astRefinementKeys.find(expr)) return *key; return nullptr; } std::optional DfgScope::lookup(Symbol symbol) const { for (const DfgScope* current = this; current; current = current->parent) { if (auto def = current->bindings.find(symbol)) return NotNull{*def}; } return std::nullopt; } std::optional DfgScope::lookup(DefId def, const std::string& key) const { for (const DfgScope* current = this; current; current = current->parent) { if (auto props = current->props.find(def)) { if (auto it = props->find(key); it != props->end()) return NotNull{it->second}; } } return std::nullopt; } void DfgScope::inherit(const DfgScope* childScope) { for (const auto& [k, a] : childScope->bindings) { if (lookup(k)) bindings[k] = a; } for (const auto& [k1, a1] : childScope->props) { for (const auto& [k2, a2] : a1) props[k1][k2] = a2; } } bool DfgScope::canUpdateDefinition(Symbol symbol) const { for (const DfgScope* current = this; current; current = current->parent) { if (current->bindings.find(symbol)) return true; else if (current->isLoopScope) return false; } return true; } bool DfgScope::canUpdateDefinition(DefId def, const std::string& key) const { for (const DfgScope* current = this; current; current = current->parent) { if (auto props = current->props.find(def)) return true; else if (current->isLoopScope) return false; } return true; } DataFlowGraph DataFlowGraphBuilder::build(AstStatBlock* block, NotNull handle) { LUAU_ASSERT(FFlag::DebugLuauDeferredConstraintResolution); DataFlowGraphBuilder builder; builder.handle = handle; builder.moduleScope = builder.childScope(nullptr); // nullptr is the root DFG scope. builder.visitBlockWithoutChildScope(builder.moduleScope, block); if (FFlag::DebugLuauFreezeArena) { builder.defArena->allocator.freeze(); builder.keyArena->allocator.freeze(); } return std::move(builder.graph); } DfgScope* DataFlowGraphBuilder::childScope(DfgScope* scope, bool isLoopScope) { return scopes.emplace_back(new DfgScope{scope, isLoopScope}).get(); } void DataFlowGraphBuilder::join(DfgScope* p, DfgScope* a, DfgScope* b) { joinBindings(p->bindings, a->bindings, b->bindings); joinProps(p->props, a->props, b->props); } void DataFlowGraphBuilder::joinBindings(DfgScope::Bindings& p, const DfgScope::Bindings& a, const DfgScope::Bindings& b) { for (const auto& [sym, def1] : a) { if (auto def2 = b.find(sym)) p[sym] = defArena->phi(NotNull{def1}, NotNull{*def2}); else if (auto def2 = p.find(sym)) p[sym] = defArena->phi(NotNull{def1}, NotNull{*def2}); } for (const auto& [sym, def1] : b) { if (auto def2 = p.find(sym)) p[sym] = defArena->phi(NotNull{def1}, NotNull{*def2}); } } void DataFlowGraphBuilder::joinProps(DfgScope::Props& p, const DfgScope::Props& a, const DfgScope::Props& b) { auto phinodify = [this](auto& p, const auto& a, const auto& b) mutable { for (const auto& [k, defA] : a) { if (auto it = b.find(k); it != b.end()) p[k] = defArena->phi(NotNull{it->second}, NotNull{defA}); else if (auto it = p.find(k); it != p.end()) p[k] = defArena->phi(NotNull{it->second}, NotNull{defA}); else p[k] = defA; } for (const auto& [k, defB] : b) { if (auto it = a.find(k); it != a.end()) continue; else if (auto it = p.find(k); it != p.end()) p[k] = defArena->phi(NotNull{it->second}, NotNull{defB}); else p[k] = defB; } }; for (const auto& [def, a1] : a) { p.try_insert(def, {}); if (auto a2 = b.find(def)) phinodify(p[def], a1, *a2); else if (auto a2 = p.find(def)) phinodify(p[def], a1, *a2); } for (const auto& [def, a1] : b) { p.try_insert(def, {}); if (a.find(def)) continue; else if (auto a2 = p.find(def)) phinodify(p[def], a1, *a2); } } DefId DataFlowGraphBuilder::lookup(DfgScope* scope, Symbol symbol) { if (auto found = scope->lookup(symbol)) return *found; else { DefId result = defArena->freshCell(); if (symbol.local) scope->bindings[symbol] = result; else moduleScope->bindings[symbol] = result; return result; } } DefId DataFlowGraphBuilder::lookup(DfgScope* scope, DefId def, const std::string& key) { if (auto found = scope->lookup(def, key)) return *found; else if (auto phi = get(def)) { std::vector defs; for (DefId operand : phi->operands) defs.push_back(lookup(scope, operand, key)); DefId result = defArena->phi(defs); scope->props[def][key] = result; return result; } else if (get(def)) { DefId result = defArena->freshCell(); scope->props[def][key] = result; return result; } else handle->ice("Inexhaustive lookup cases in DataFlowGraphBuilder::lookup"); } ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStatBlock* b) { DfgScope* child = childScope(scope); ControlFlow cf = visitBlockWithoutChildScope(child, b); scope->inherit(child); return cf; } ControlFlow DataFlowGraphBuilder::visitBlockWithoutChildScope(DfgScope* scope, AstStatBlock* b) { std::optional firstControlFlow; for (AstStat* stat : b->body) { ControlFlow cf = visit(scope, stat); if (cf != ControlFlow::None && !firstControlFlow) firstControlFlow = cf; } return firstControlFlow.value_or(ControlFlow::None); } ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStat* s) { if (auto b = s->as()) return visit(scope, b); else if (auto i = s->as()) return visit(scope, i); else if (auto w = s->as()) return visit(scope, w); else if (auto r = s->as()) return visit(scope, r); else if (auto b = s->as()) return visit(scope, b); else if (auto c = s->as()) return visit(scope, c); else if (auto r = s->as()) return visit(scope, r); else if (auto e = s->as()) return visit(scope, e); else if (auto l = s->as()) return visit(scope, l); else if (auto f = s->as()) return visit(scope, f); else if (auto f = s->as()) return visit(scope, f); else if (auto a = s->as()) return visit(scope, a); else if (auto c = s->as()) return visit(scope, c); else if (auto f = s->as()) return visit(scope, f); else if (auto l = s->as()) return visit(scope, l); else if (auto t = s->as()) return visit(scope, t); else if (auto d = s->as()) return visit(scope, d); else if (auto d = s->as()) return visit(scope, d); else if (auto d = s->as()) return visit(scope, d); else if (auto error = s->as()) return visit(scope, error); else handle->ice("Unknown AstStat in DataFlowGraphBuilder::visit"); } ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStatIf* i) { visitExpr(scope, i->condition); DfgScope* thenScope = childScope(scope); DfgScope* elseScope = childScope(scope); ControlFlow thencf = visit(thenScope, i->thenbody); ControlFlow elsecf = ControlFlow::None; if (i->elsebody) elsecf = visit(elseScope, i->elsebody); if (thencf != ControlFlow::None && elsecf == ControlFlow::None) join(scope, scope, elseScope); else if (thencf == ControlFlow::None && elsecf != ControlFlow::None) join(scope, thenScope, scope); else if ((thencf | elsecf) == ControlFlow::None) join(scope, thenScope, elseScope); if (FFlag::LuauLoopControlFlowAnalysis && thencf == elsecf) return thencf; else if (matches(thencf, ControlFlow::Returns | ControlFlow::Throws) && matches(elsecf, ControlFlow::Returns | ControlFlow::Throws)) return ControlFlow::Returns; else return ControlFlow::None; } ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStatWhile* w) { // TODO(controlflow): entry point has a back edge from exit point DfgScope* whileScope = childScope(scope, /*isLoopScope=*/true); visitExpr(whileScope, w->condition); visit(whileScope, w->body); scope->inherit(whileScope); return ControlFlow::None; } ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStatRepeat* r) { // TODO(controlflow): entry point has a back edge from exit point DfgScope* repeatScope = childScope(scope, /*isLoopScope=*/true); visitBlockWithoutChildScope(repeatScope, r->body); visitExpr(repeatScope, r->condition); scope->inherit(repeatScope); return ControlFlow::None; } ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStatBreak* b) { return ControlFlow::Breaks; } ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStatContinue* c) { return ControlFlow::Continues; } ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStatReturn* r) { for (AstExpr* e : r->list) visitExpr(scope, e); return ControlFlow::Returns; } ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStatExpr* e) { visitExpr(scope, e->expr); if (auto call = e->expr->as(); call && doesCallError(call)) return ControlFlow::Throws; else return ControlFlow::None; } ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStatLocal* l) { // We're gonna need a `visitExprList` and `visitVariadicExpr` (function calls and `...`) std::vector defs; defs.reserve(l->values.size); for (AstExpr* e : l->values) defs.push_back(visitExpr(scope, e).def); for (size_t i = 0; i < l->vars.size; ++i) { AstLocal* local = l->vars.data[i]; if (local->annotation) visitType(scope, local->annotation); // We need to create a new def to intentionally avoid alias tracking, but we'd like to // make sure that the non-aliased defs are also marked as a subscript for refinements. bool subscripted = i < defs.size() && containsSubscriptedDefinition(defs[i]); DefId def = defArena->freshCell(subscripted); graph.localDefs[local] = def; scope->bindings[local] = def; } return ControlFlow::None; } ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStatFor* f) { DfgScope* forScope = childScope(scope, /*isLoopScope=*/true); visitExpr(scope, f->from); visitExpr(scope, f->to); if (f->step) visitExpr(scope, f->step); if (f->var->annotation) visitType(forScope, f->var->annotation); DefId def = defArena->freshCell(); graph.localDefs[f->var] = def; scope->bindings[f->var] = def; // TODO(controlflow): entry point has a back edge from exit point visit(forScope, f->body); scope->inherit(forScope); return ControlFlow::None; } ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStatForIn* f) { DfgScope* forScope = childScope(scope, /*isLoopScope=*/true); for (AstLocal* local : f->vars) { if (local->annotation) visitType(forScope, local->annotation); DefId def = defArena->freshCell(); graph.localDefs[local] = def; forScope->bindings[local] = def; } // TODO(controlflow): entry point has a back edge from exit point // We're gonna need a `visitExprList` and `visitVariadicExpr` (function calls and `...`) for (AstExpr* e : f->values) visitExpr(forScope, e); visit(forScope, f->body); scope->inherit(forScope); return ControlFlow::None; } ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStatAssign* a) { std::vector defs; defs.reserve(a->values.size); for (AstExpr* e : a->values) defs.push_back(visitExpr(scope, e).def); for (size_t i = 0; i < a->vars.size; ++i) { AstExpr* v = a->vars.data[i]; visitLValue(scope, v, i < defs.size() ? defs[i] : defArena->freshCell()); } return ControlFlow::None; } ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStatCompoundAssign* c) { // TODO: This needs revisiting because this is incorrect. The `c->var` part is both being read and written to, // but the `c->var` only has one pointer address, so we need to come up with a way to store both. // For now, it's not important because we don't have type states, but it is going to be important, e.g. // // local a = 5 -- a-1 // a += 5 -- a-2 = a-1 + 5 // We can't just visit `c->var` as a rvalue and then separately traverse `c->var` as an lvalue, since that's O(n^2). DefId def = visitExpr(scope, c->value).def; visitLValue(scope, c->var, def, /* isCompoundAssignment */ true); return ControlFlow::None; } ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStatFunction* f) { // In the old solver, we assumed that the name of the function is always a function in the body // but this isn't true, e.g. the following example will print `5`, not a function address. // // local function f() print(f) end // local g = f // f = 5 // g() --> 5 // // which is evidence that references to variables must be a phi node of all possible definitions, // but for bug compatibility, we'll assume the same thing here. DefId prototype = defArena->freshCell(); visitLValue(scope, f->name, prototype); visitExpr(scope, f->func); return ControlFlow::None; } ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStatLocalFunction* l) { DefId def = defArena->freshCell(); graph.localDefs[l->name] = def; scope->bindings[l->name] = def; visitExpr(scope, l->func); return ControlFlow::None; } ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStatTypeAlias* t) { DfgScope* unreachable = childScope(scope); visitGenerics(unreachable, t->generics); visitGenericPacks(unreachable, t->genericPacks); visitType(unreachable, t->type); return ControlFlow::None; } ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStatDeclareGlobal* d) { DefId def = defArena->freshCell(); graph.declaredDefs[d] = def; scope->bindings[d->name] = def; visitType(scope, d->type); return ControlFlow::None; } ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStatDeclareFunction* d) { DefId def = defArena->freshCell(); graph.declaredDefs[d] = def; scope->bindings[d->name] = def; DfgScope* unreachable = childScope(scope); visitGenerics(unreachable, d->generics); visitGenericPacks(unreachable, d->genericPacks); visitTypeList(unreachable, d->params); visitTypeList(unreachable, d->retTypes); return ControlFlow::None; } ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStatDeclareClass* d) { // This declaration does not "introduce" any bindings in value namespace, // so there's no symbolic value to begin with. We'll traverse the properties // because their type annotations may depend on something in the value namespace. DfgScope* unreachable = childScope(scope); for (AstDeclaredClassProp prop : d->props) visitType(unreachable, prop.ty); return ControlFlow::None; } ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStatError* error) { DfgScope* unreachable = childScope(scope); for (AstStat* s : error->statements) visit(unreachable, s); for (AstExpr* e : error->expressions) visitExpr(unreachable, e); return ControlFlow::None; } DataFlowResult DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExpr* e) { // Some subexpressions could be visited two times. If we've already seen it, just extract it. if (auto def = graph.astDefs.find(e)) { auto key = graph.astRefinementKeys.find(e); return {NotNull{*def}, key ? *key : nullptr}; } auto go = [&]() -> DataFlowResult { if (auto g = e->as()) return visitExpr(scope, g); else if (auto c = e->as()) return {defArena->freshCell(), nullptr}; // ok else if (auto c = e->as()) return {defArena->freshCell(), nullptr}; // ok else if (auto c = e->as()) return {defArena->freshCell(), nullptr}; // ok else if (auto c = e->as()) return {defArena->freshCell(), nullptr}; // ok else if (auto l = e->as()) return visitExpr(scope, l); else if (auto g = e->as()) return visitExpr(scope, g); else if (auto v = e->as()) return {defArena->freshCell(), nullptr}; // ok else if (auto c = e->as()) return visitExpr(scope, c); else if (auto i = e->as()) return visitExpr(scope, i); else if (auto i = e->as()) return visitExpr(scope, i); else if (auto f = e->as()) return visitExpr(scope, f); else if (auto t = e->as()) return visitExpr(scope, t); else if (auto u = e->as()) return visitExpr(scope, u); else if (auto b = e->as()) return visitExpr(scope, b); else if (auto t = e->as()) return visitExpr(scope, t); else if (auto i = e->as()) return visitExpr(scope, i); else if (auto i = e->as()) return visitExpr(scope, i); else if (auto error = e->as()) return visitExpr(scope, error); else handle->ice("Unknown AstExpr in DataFlowGraphBuilder::visitExpr"); }; auto [def, key] = go(); graph.astDefs[e] = def; if (key) graph.astRefinementKeys[e] = key; return {def, key}; } DataFlowResult DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExprGroup* group) { return visitExpr(scope, group->expr); } DataFlowResult DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExprLocal* l) { // DfgScope::lookup is intentional here: we want to be able to ice. if (auto def = scope->lookup(l->local)) { const RefinementKey* key = keyArena->leaf(*def); return {*def, key}; } handle->ice("DFG: AstExprLocal came before its declaration?"); } DataFlowResult DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExprGlobal* g) { DefId def = lookup(scope, g->name); return {def, keyArena->leaf(def)}; } DataFlowResult DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExprCall* c) { visitExpr(scope, c->func); for (AstExpr* arg : c->args) visitExpr(scope, arg); return {defArena->freshCell(), nullptr}; } DataFlowResult DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExprIndexName* i) { auto [parentDef, parentKey] = visitExpr(scope, i->expr); std::string index = i->index.value; DefId def = lookup(scope, parentDef, index); return {def, keyArena->node(parentKey, def, index)}; } DataFlowResult DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExprIndexExpr* i) { auto [parentDef, parentKey] = visitExpr(scope, i->expr); visitExpr(scope, i->index); if (auto string = i->index->as()) { std::string index{string->value.data, string->value.size}; DefId def = lookup(scope, parentDef, index); return {def, keyArena->node(parentKey, def, index)}; } return {defArena->freshCell(/* subscripted= */true), nullptr}; } DataFlowResult DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExprFunction* f) { DfgScope* signatureScope = childScope(scope); if (AstLocal* self = f->self) { // There's no syntax for `self` to have an annotation if using `function t:m()` LUAU_ASSERT(!self->annotation); DefId def = defArena->freshCell(); graph.localDefs[self] = def; signatureScope->bindings[self] = def; } for (AstLocal* param : f->args) { if (param->annotation) visitType(signatureScope, param->annotation); DefId def = defArena->freshCell(); graph.localDefs[param] = def; signatureScope->bindings[param] = def; } if (f->varargAnnotation) visitTypePack(scope, f->varargAnnotation); if (f->returnAnnotation) visitTypeList(signatureScope, *f->returnAnnotation); // TODO: function body can be re-entrant, as in mutations that occurs at the end of the function can also be // visible to the beginning of the function, so statically speaking, the body of the function has an exit point // that points back to itself, e.g. // // local function f() print(f) f = 5 end // local g = f // g() --> function: address // g() --> 5 visit(signatureScope, f->body); return {defArena->freshCell(), nullptr}; } DataFlowResult DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExprTable* t) { for (AstExprTable::Item item : t->items) { if (item.key) visitExpr(scope, item.key); visitExpr(scope, item.value); } return {defArena->freshCell(), nullptr}; } DataFlowResult DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExprUnary* u) { visitExpr(scope, u->expr); return {defArena->freshCell(), nullptr}; } DataFlowResult DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExprBinary* b) { visitExpr(scope, b->left); visitExpr(scope, b->right); return {defArena->freshCell(), nullptr}; } DataFlowResult DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExprTypeAssertion* t) { auto [def, key] = visitExpr(scope, t->expr); visitType(scope, t->annotation); return {def, key}; } DataFlowResult DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExprIfElse* i) { visitExpr(scope, i->condition); visitExpr(scope, i->trueExpr); visitExpr(scope, i->falseExpr); return {defArena->freshCell(), nullptr}; } DataFlowResult DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExprInterpString* i) { for (AstExpr* e : i->expressions) visitExpr(scope, e); return {defArena->freshCell(), nullptr}; } DataFlowResult DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExprError* error) { DfgScope* unreachable = childScope(scope); for (AstExpr* e : error->expressions) visitExpr(unreachable, e); return {defArena->freshCell(), nullptr}; } void DataFlowGraphBuilder::visitLValue(DfgScope* scope, AstExpr* e, DefId incomingDef, bool isCompoundAssignment) { if (auto l = e->as()) return visitLValue(scope, l, incomingDef, isCompoundAssignment); else if (auto g = e->as()) return visitLValue(scope, g, incomingDef, isCompoundAssignment); else if (auto i = e->as()) return visitLValue(scope, i, incomingDef); else if (auto i = e->as()) return visitLValue(scope, i, incomingDef); else if (auto error = e->as()) return visitLValue(scope, error, incomingDef); else handle->ice("Unknown AstExpr in DataFlowGraphBuilder::visitLValue"); } void DataFlowGraphBuilder::visitLValue(DfgScope* scope, AstExprLocal* l, DefId incomingDef, bool isCompoundAssignment) { // We need to keep the previous def around for a compound assignment. if (isCompoundAssignment) { if (auto def = scope->lookup(l->local)) graph.compoundAssignDefs[l] = *def; } // In order to avoid alias tracking, we need to clip the reference to the parent def. if (scope->canUpdateDefinition(l->local)) { DefId updated = defArena->freshCell(containsSubscriptedDefinition(incomingDef)); graph.astDefs[l] = updated; scope->bindings[l->local] = updated; } else visitExpr(scope, static_cast(l)); } void DataFlowGraphBuilder::visitLValue(DfgScope* scope, AstExprGlobal* g, DefId incomingDef, bool isCompoundAssignment) { // We need to keep the previous def around for a compound assignment. if (isCompoundAssignment) { DefId def = lookup(scope, g->name); graph.compoundAssignDefs[g] = def; } // In order to avoid alias tracking, we need to clip the reference to the parent def. if (scope->canUpdateDefinition(g->name)) { DefId updated = defArena->freshCell(containsSubscriptedDefinition(incomingDef)); graph.astDefs[g] = updated; scope->bindings[g->name] = updated; } else visitExpr(scope, static_cast(g)); } void DataFlowGraphBuilder::visitLValue(DfgScope* scope, AstExprIndexName* i, DefId incomingDef) { DefId parentDef = visitExpr(scope, i->expr).def; if (scope->canUpdateDefinition(parentDef, i->index.value)) { DefId updated = defArena->freshCell(containsSubscriptedDefinition(incomingDef)); graph.astDefs[i] = updated; scope->props[parentDef][i->index.value] = updated; } else visitExpr(scope, static_cast(i)); } void DataFlowGraphBuilder::visitLValue(DfgScope* scope, AstExprIndexExpr* i, DefId incomingDef) { DefId parentDef = visitExpr(scope, i->expr).def; visitExpr(scope, i->index); if (auto string = i->index->as()) { if (scope->canUpdateDefinition(parentDef, string->value.data)) { DefId updated = defArena->freshCell(containsSubscriptedDefinition(incomingDef)); graph.astDefs[i] = updated; scope->props[parentDef][string->value.data] = updated; } else visitExpr(scope, static_cast(i)); } graph.astDefs[i] = defArena->freshCell(); } void DataFlowGraphBuilder::visitLValue(DfgScope* scope, AstExprError* error, DefId incomingDef) { DefId def = visitExpr(scope, error).def; graph.astDefs[error] = def; } void DataFlowGraphBuilder::visitType(DfgScope* scope, AstType* t) { if (auto r = t->as()) return visitType(scope, r); else if (auto table = t->as()) return visitType(scope, table); else if (auto f = t->as()) return visitType(scope, f); else if (auto tyof = t->as()) return visitType(scope, tyof); else if (auto u = t->as()) return visitType(scope, u); else if (auto i = t->as()) return visitType(scope, i); else if (auto e = t->as()) return visitType(scope, e); else if (auto s = t->as()) return; // ok else if (auto s = t->as()) return; // ok else handle->ice("Unknown AstType in DataFlowGraphBuilder::visitType"); } void DataFlowGraphBuilder::visitType(DfgScope* scope, AstTypeReference* r) { for (AstTypeOrPack param : r->parameters) { if (param.type) visitType(scope, param.type); else visitTypePack(scope, param.typePack); } } void DataFlowGraphBuilder::visitType(DfgScope* scope, AstTypeTable* t) { for (AstTableProp p : t->props) visitType(scope, p.type); if (t->indexer) { visitType(scope, t->indexer->indexType); visitType(scope, t->indexer->resultType); } } void DataFlowGraphBuilder::visitType(DfgScope* scope, AstTypeFunction* f) { visitGenerics(scope, f->generics); visitGenericPacks(scope, f->genericPacks); visitTypeList(scope, f->argTypes); visitTypeList(scope, f->returnTypes); } void DataFlowGraphBuilder::visitType(DfgScope* scope, AstTypeTypeof* t) { visitExpr(scope, t->expr); } void DataFlowGraphBuilder::visitType(DfgScope* scope, AstTypeUnion* u) { for (AstType* t : u->types) visitType(scope, t); } void DataFlowGraphBuilder::visitType(DfgScope* scope, AstTypeIntersection* i) { for (AstType* t : i->types) visitType(scope, t); } void DataFlowGraphBuilder::visitType(DfgScope* scope, AstTypeError* error) { for (AstType* t : error->types) visitType(scope, t); } void DataFlowGraphBuilder::visitTypePack(DfgScope* scope, AstTypePack* p) { if (auto e = p->as()) return visitTypePack(scope, e); else if (auto v = p->as()) return visitTypePack(scope, v); else if (auto g = p->as()) return; // ok else handle->ice("Unknown AstTypePack in DataFlowGraphBuilder::visitTypePack"); } void DataFlowGraphBuilder::visitTypePack(DfgScope* scope, AstTypePackExplicit* e) { visitTypeList(scope, e->typeList); } void DataFlowGraphBuilder::visitTypePack(DfgScope* scope, AstTypePackVariadic* v) { visitType(scope, v->variadicType); } void DataFlowGraphBuilder::visitTypeList(DfgScope* scope, AstTypeList l) { for (AstType* t : l.types) visitType(scope, t); if (l.tailType) visitTypePack(scope, l.tailType); } void DataFlowGraphBuilder::visitGenerics(DfgScope* scope, AstArray g) { for (AstGenericType generic : g) { if (generic.defaultValue) visitType(scope, generic.defaultValue); } } void DataFlowGraphBuilder::visitGenericPacks(DfgScope* scope, AstArray g) { for (AstGenericTypePack generic : g) { if (generic.defaultValue) visitTypePack(scope, generic.defaultValue); } } } // namespace Luau