Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Roblox
GitHub Repository: Roblox/luau
Path: blob/master/Analysis/src/FragmentAutocomplete.cpp
2725 views
1
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
2
#include "Luau/FragmentAutocomplete.h"
3
4
#include "Luau/Ast.h"
5
#include "Luau/AstQuery.h"
6
#include "Luau/Autocomplete.h"
7
#include "Luau/Common.h"
8
#include "Luau/ExpectedTypeVisitor.h"
9
#include "Luau/ModuleResolver.h"
10
#include "Luau/Parser.h"
11
#include "Luau/ParseOptions.h"
12
#include "Luau/Module.h"
13
#include "Luau/RequireTracer.h"
14
#include "Luau/TimeTrace.h"
15
#include "Luau/UnifierSharedState.h"
16
#include "Luau/TypeFunction.h"
17
#include "Luau/DataFlowGraph.h"
18
#include "Luau/ConstraintGenerator.h"
19
#include "Luau/ConstraintSolver.h"
20
#include "Luau/Frontend.h"
21
#include "Luau/Parser.h"
22
#include "Luau/ParseOptions.h"
23
#include "Luau/Module.h"
24
#include "Luau/Clone.h"
25
#include "AutocompleteCore.h"
26
#include <optional>
27
28
LUAU_FASTINT(LuauTypeInferRecursionLimit);
29
LUAU_FASTINT(LuauTypeInferIterationLimit);
30
LUAU_FASTINT(LuauTarjanChildLimit)
31
32
LUAU_FASTFLAGVARIABLE(DebugLogFragmentsFromAutocomplete)
33
LUAU_FASTFLAG(LuauOverloadGetsInstantiated)
34
35
namespace Luau
36
{
37
38
static std::pair<size_t, size_t> getDocumentOffsets(std::string_view src, const Position& startPos, const Position& endPos);
39
40
41
// when typing a function partially, get the span of the first line
42
// e.g. local function fn() : ... - typically we want to provide autocomplete results if you're
43
// editing type annotations in this range
44
Location getFunctionDeclarationExtents(AstExprFunction* exprFn, AstExpr* exprName = nullptr, AstLocal* localName = nullptr)
45
{
46
auto fnBegin = exprFn->location.begin;
47
auto fnEnd = exprFn->location.end;
48
if (auto returnAnnot = exprFn->returnAnnotation)
49
{
50
fnEnd = returnAnnot->location.end;
51
}
52
else if (exprFn->args.size != 0)
53
{
54
auto last = exprFn->args.data[exprFn->args.size - 1];
55
if (last->annotation)
56
fnEnd = last->annotation->location.end;
57
else
58
fnEnd = last->location.end;
59
}
60
else if (exprFn->genericPacks.size != 0)
61
fnEnd = exprFn->genericPacks.data[exprFn->genericPacks.size - 1]->location.end;
62
else if (exprFn->generics.size != 0)
63
fnEnd = exprFn->generics.data[exprFn->generics.size - 1]->location.end;
64
else if (exprName)
65
fnEnd = exprName->location.end;
66
else if (localName)
67
fnEnd = localName->location.end;
68
return Location{fnBegin, fnEnd};
69
};
70
71
Location getAstStatForExtents(AstStatFor* forStat)
72
{
73
auto begin = forStat->location.begin;
74
auto end = forStat->location.end;
75
if (forStat->step)
76
end = forStat->step->location.end;
77
else if (forStat->to)
78
end = forStat->to->location.end;
79
else if (forStat->from)
80
end = forStat->from->location.end;
81
else if (forStat->var)
82
end = forStat->var->location.end;
83
84
return Location{begin, end};
85
}
86
87
// Traverses the linkedlist of else if statements to find the one that matches the cursor position the best
88
AstStatIf* getNearestIfToCursor(AstStat* stmt, const Position& cursorPos)
89
{
90
AstStatIf* current = stmt->as<AstStatIf>();
91
if (!current)
92
return nullptr;
93
while (current->is<AstStatIf>())
94
{
95
if (current->elsebody && current->elsebody->location.containsClosed(cursorPos))
96
{
97
if (auto elseIfS = current->elsebody->as<AstStatIf>())
98
{
99
current = elseIfS;
100
}
101
else
102
break;
103
}
104
else
105
break;
106
}
107
return current;
108
}
109
110
Location getFragmentLocation(AstStat* nearestStatement, const Position& cursorPosition)
111
{
112
Location empty{cursorPosition, cursorPosition};
113
if (nearestStatement)
114
{
115
Location nonEmpty{nearestStatement->location.begin, cursorPosition};
116
// If your sibling is a do block, do nothing
117
if (nearestStatement->as<AstStatBlock>())
118
return empty;
119
120
// If you're inside the body of the function and this is your sibling, empty fragment
121
// If you're outside the body (e.g. you're typing stuff out, non-empty)
122
if (auto fn = nearestStatement->as<AstStatFunction>())
123
{
124
auto loc = getFunctionDeclarationExtents(fn->func, fn->name, /* local */ nullptr);
125
if (loc.containsClosed(cursorPosition))
126
return nonEmpty;
127
else if (fn->func->body->location.containsClosed(cursorPosition) || fn->location.end <= cursorPosition)
128
return empty;
129
else if (fn->func->location.contains(cursorPosition))
130
return nonEmpty;
131
}
132
133
if (auto fn = nearestStatement->as<AstStatLocalFunction>())
134
{
135
auto loc = getFunctionDeclarationExtents(fn->func, /* global func */ nullptr, fn->name);
136
if (loc.containsClosed(cursorPosition))
137
return nonEmpty;
138
else if (fn->func->body->location.containsClosed(cursorPosition) || fn->location.end <= cursorPosition)
139
return empty;
140
else if (fn->func->location.contains(cursorPosition))
141
return nonEmpty;
142
}
143
144
if (auto wh = nearestStatement->as<AstStatWhile>())
145
{
146
if (!wh->hasDo)
147
return nonEmpty;
148
else
149
return empty;
150
}
151
152
if (auto forStat = nearestStatement->as<AstStatFor>())
153
{
154
if (forStat->step && forStat->step->location.containsClosed(cursorPosition))
155
return {forStat->step->location.begin, cursorPosition};
156
if (forStat->to && forStat->to->location.containsClosed(cursorPosition))
157
return {forStat->to->location.begin, cursorPosition};
158
if (forStat->from && forStat->from->location.containsClosed(cursorPosition))
159
return {forStat->from->location.begin, cursorPosition};
160
161
if (!forStat->hasDo)
162
return nonEmpty;
163
else
164
{
165
auto completeableExtents = Location{forStat->location.begin, forStat->doLocation.begin};
166
if (completeableExtents.containsClosed(cursorPosition))
167
return nonEmpty;
168
169
return empty;
170
}
171
}
172
173
if (auto forIn = nearestStatement->as<AstStatForIn>())
174
{
175
if (!forIn->hasDo)
176
return nonEmpty;
177
else
178
{
179
auto completeableExtents = Location{forIn->location.begin, forIn->doLocation.begin};
180
if (completeableExtents.containsClosed(cursorPosition))
181
{
182
if (!forIn->hasIn)
183
return nonEmpty;
184
else
185
{
186
// [for ... in ... do] - the cursor can either be between [for ... in] or [in ... do]
187
if (cursorPosition < forIn->inLocation.begin)
188
return nonEmpty;
189
else
190
return Location{forIn->inLocation.begin, cursorPosition};
191
}
192
}
193
return empty;
194
}
195
}
196
if (auto ifS = getNearestIfToCursor(nearestStatement, cursorPosition))
197
{
198
auto conditionExtents = Location{ifS->condition->location.begin, ifS->condition->location.end};
199
if (conditionExtents.containsClosed(cursorPosition) || !ifS->thenLocation)
200
{
201
// CLI-152249 - the condition parse location can sometimes be after the body of the if
202
// statement. This is a bug that results returning locations like {3,0 - 2,0} which is
203
// wrong.
204
if (ifS->condition->location.begin > cursorPosition)
205
return empty;
206
return Location{ifS->condition->location.begin, cursorPosition};
207
}
208
209
else if (ifS->thenbody->location.containsClosed(cursorPosition))
210
return empty;
211
else if (auto elseS = ifS->elsebody)
212
{
213
if (auto elseIf = ifS->elsebody->as<AstStatIf>())
214
{
215
auto elseIfConditionExtents = Location{elseIf->location.begin, elseIf->condition->location.end};
216
if (elseIfConditionExtents.containsClosed(cursorPosition))
217
return {elseIf->condition->location.begin, cursorPosition};
218
if (elseIf->thenbody->hasEnd)
219
return empty;
220
else
221
return {elseS->location.begin, cursorPosition};
222
}
223
return empty;
224
}
225
}
226
227
return nonEmpty;
228
}
229
return empty;
230
}
231
232
struct NearestStatementFinder : public AstVisitor
233
{
234
explicit NearestStatementFinder(const Position& cursorPosition)
235
: cursor(cursorPosition)
236
{
237
}
238
239
bool visit(AstStatBlock* block) override
240
{
241
if (block->location.containsClosed(cursor))
242
{
243
parent = block;
244
for (auto v : block->body)
245
{
246
if (v->location.begin <= cursor)
247
{
248
nearest = v;
249
}
250
}
251
return true;
252
}
253
else
254
return false;
255
}
256
257
const Position& cursor;
258
AstStat* nearest = nullptr;
259
AstStatBlock* parent = nullptr;
260
};
261
262
// This struct takes a block found in a updated AST and looks for the corresponding block in a different ast.
263
// This is a best effort check - we are looking for the block that is as close in location, ideally the same
264
// block as the one from the updated AST
265
struct NearestLikelyBlockFinder : public AstVisitor
266
{
267
explicit NearestLikelyBlockFinder(NotNull<AstStatBlock> stmtBlockRecentAst)
268
: stmtBlockRecentAst(stmtBlockRecentAst)
269
{
270
}
271
272
bool visit(AstStatBlock* block) override
273
{
274
if (block->location.begin <= stmtBlockRecentAst->location.begin)
275
{
276
if (found)
277
{
278
if (found.value()->location.begin < block->location.begin)
279
found.emplace(block);
280
}
281
else
282
{
283
found.emplace(block);
284
}
285
}
286
287
return true;
288
}
289
NotNull<AstStatBlock> stmtBlockRecentAst;
290
std::optional<AstStatBlock*> found = std::nullopt;
291
};
292
293
// Diffs two ast stat blocks. Once at the first difference, consume between that range and the end of the nearest statement
294
std::optional<Position> blockDiffStart(AstStatBlock* blockOld, AstStatBlock* blockNew, AstStat* nearestStatementNewAst)
295
{
296
AstArray<AstStat*> _old = blockOld->body;
297
AstArray<AstStat*> _new = blockNew->body;
298
size_t oldSize = _old.size;
299
size_t stIndex = 0;
300
301
// We couldn't find a nearest statement
302
if (nearestStatementNewAst == blockNew)
303
return std::nullopt;
304
bool found = false;
305
for (auto st : _new)
306
{
307
if (st == nearestStatementNewAst)
308
{
309
found = true;
310
break;
311
}
312
stIndex++;
313
}
314
315
if (!found)
316
return std::nullopt;
317
// Take care of some easy cases!
318
if (oldSize == 0 && _new.size >= 0)
319
return {_new.data[0]->location.begin};
320
321
if (_new.size < oldSize)
322
return std::nullopt;
323
324
for (size_t i = 0; i < std::min(oldSize, stIndex + 1); i++)
325
{
326
AstStat* oldStat = _old.data[i];
327
AstStat* newStat = _new.data[i];
328
329
bool isSame = oldStat->classIndex == newStat->classIndex && oldStat->location == newStat->location;
330
if (!isSame)
331
return {oldStat->location.begin};
332
}
333
334
if (oldSize <= stIndex)
335
return {_new.data[oldSize]->location.begin};
336
337
return std::nullopt;
338
}
339
340
341
FragmentRegion getFragmentRegion(AstStatBlock* root, const Position& cursorPosition)
342
{
343
NearestStatementFinder nsf{cursorPosition};
344
root->visit(&nsf);
345
AstStatBlock* parent = root;
346
if (nsf.parent)
347
parent = nsf.parent;
348
return FragmentRegion{getFragmentLocation(nsf.nearest, cursorPosition), nsf.nearest, parent};
349
};
350
351
FragmentRegion getFragmentRegionWithBlockDiff(AstStatBlock* stale, AstStatBlock* fresh, const Position& cursorPos)
352
{
353
// Visit the new ast
354
NearestStatementFinder nsf{cursorPos};
355
fresh->visit(&nsf);
356
// parent must always be non-null
357
NotNull<AstStatBlock> parent{nsf.parent ? nsf.parent : fresh};
358
NotNull<AstStat> nearest{nsf.nearest ? nsf.nearest : fresh};
359
// Grab the same start block in the stale ast
360
NearestLikelyBlockFinder lsf{parent};
361
stale->visit(&lsf);
362
363
if (auto sameBlock = lsf.found)
364
{
365
if (std::optional<Position> fd = blockDiffStart(*sameBlock, parent, nearest))
366
return FragmentRegion{Location{*fd, cursorPos}, nearest, parent};
367
}
368
return FragmentRegion{getFragmentLocation(nsf.nearest, cursorPos), nearest, parent};
369
}
370
371
FragmentAutocompleteAncestryResult findAncestryForFragmentParse(AstStatBlock* stale, const Position& cursorPos, AstStatBlock* lastGoodParse)
372
{
373
// the freshest ast can sometimes be null if the parse was bad.
374
if (lastGoodParse == nullptr)
375
return {};
376
FragmentRegion region = getFragmentRegionWithBlockDiff(stale, lastGoodParse, cursorPos);
377
std::vector<AstNode*> ancestry = findAncestryAtPositionForAutocomplete(stale, cursorPos);
378
LUAU_ASSERT(ancestry.size() >= 1);
379
// We should only pick up locals that are before the region
380
DenseHashMap<AstName, AstLocal*> localMap{AstName()};
381
std::vector<AstLocal*> localStack;
382
383
for (AstNode* node : ancestry)
384
{
385
if (auto block = node->as<AstStatBlock>())
386
{
387
for (auto stat : block->body)
388
{
389
if (stat->location.begin < region.fragmentLocation.begin)
390
{
391
// This statement precedes the current one
392
if (auto statLoc = stat->as<AstStatLocal>())
393
{
394
for (auto v : statLoc->vars)
395
{
396
localStack.push_back(v);
397
localMap[v->name] = v;
398
}
399
}
400
else if (auto locFun = stat->as<AstStatLocalFunction>())
401
{
402
localStack.push_back(locFun->name);
403
localMap[locFun->name->name] = locFun->name;
404
if (locFun->location.contains(cursorPos))
405
{
406
for (AstLocal* loc : locFun->func->args)
407
{
408
localStack.push_back(loc);
409
localMap[loc->name] = loc;
410
}
411
}
412
}
413
else if (auto globFun = stat->as<AstStatFunction>())
414
{
415
if (globFun->location.contains(cursorPos))
416
{
417
if (auto local = globFun->func->self)
418
{
419
localStack.push_back(local);
420
localMap[local->name] = local;
421
}
422
423
for (AstLocal* loc : globFun->func->args)
424
{
425
localStack.push_back(loc);
426
localMap[loc->name] = loc;
427
}
428
}
429
}
430
else if (auto typeFun = stat->as<AstStatTypeFunction>(); typeFun)
431
{
432
if (typeFun->location.contains(cursorPos))
433
{
434
for (AstLocal* loc : typeFun->body->args)
435
{
436
localStack.push_back(loc);
437
localMap[loc->name] = loc;
438
}
439
}
440
}
441
else if (auto forL = stat->as<AstStatFor>())
442
{
443
if (forL->var && forL->var->location.begin < region.fragmentLocation.begin)
444
{
445
localStack.push_back(forL->var);
446
localMap[forL->var->name] = forL->var;
447
}
448
}
449
else if (auto forIn = stat->as<AstStatForIn>())
450
{
451
for (auto var : forIn->vars)
452
{
453
if (var->location.begin < region.fragmentLocation.begin)
454
{
455
localStack.push_back(var);
456
localMap[var->name] = var;
457
}
458
}
459
}
460
}
461
}
462
}
463
464
if (auto exprFunc = node->as<AstExprFunction>())
465
{
466
if (exprFunc->location.contains(cursorPos))
467
{
468
for (auto v : exprFunc->args)
469
{
470
localStack.push_back(v);
471
localMap[v->name] = v;
472
}
473
}
474
}
475
}
476
477
return {std::move(localMap), std::move(localStack), std::move(ancestry), region.nearestStatement, region.parentBlock, region.fragmentLocation};
478
}
479
480
std::optional<FragmentParseResult> parseFragment(
481
AstStatBlock* stale,
482
AstStatBlock* mostRecentParse,
483
AstNameTable* names,
484
std::string_view src,
485
const Position& cursorPos,
486
std::optional<Position> fragmentEndPosition
487
)
488
{
489
if (mostRecentParse == nullptr)
490
return std::nullopt;
491
FragmentAutocompleteAncestryResult result = findAncestryForFragmentParse(stale, cursorPos, mostRecentParse);
492
AstStat* nearestStatement = result.nearestStatement;
493
494
Position startPos = result.fragmentSelectionRegion.begin;
495
Position endPos = fragmentEndPosition.value_or(result.fragmentSelectionRegion.end);
496
497
auto [offsetStart, parseLength] = getDocumentOffsets(src, startPos, endPos);
498
const char* srcStart = src.data() + offsetStart;
499
std::string_view dbg = src.substr(offsetStart, parseLength);
500
FragmentParseResult fragmentResult;
501
fragmentResult.fragmentToParse = std::string(dbg);
502
// For the duration of the incremental parse, we want to allow the name table to re-use duplicate names
503
if (FFlag::DebugLogFragmentsFromAutocomplete)
504
logLuau("Fragment Selected", dbg);
505
506
ParseOptions opts;
507
opts.allowDeclarationSyntax = false;
508
opts.captureComments = true;
509
opts.parseFragment = FragmentParseResumeSettings{std::move(result.localMap), std::move(result.localStack), startPos};
510
ParseResult p = Luau::Parser::parse(srcStart, parseLength, *names, *fragmentResult.alloc, std::move(opts));
511
// This means we threw a ParseError and we should decline to offer autocomplete here.
512
if (p.root == nullptr)
513
return std::nullopt;
514
515
std::vector<AstNode*> fabricatedAncestry = findAncestryAtPositionForAutocomplete(mostRecentParse, cursorPos);
516
std::vector<AstNode*> fragmentAncestry = findAncestryAtPositionForAutocomplete(p.root, cursorPos);
517
518
// Computes the accurate ancestry and then replaces the nodes that correspond to the fragment ancestry
519
// Needed because we look up types by pointer identity
520
LUAU_ASSERT(!fabricatedAncestry.empty());
521
auto back = fabricatedAncestry.size() - 1;
522
for (auto it = fragmentAncestry.rbegin(); it != fragmentAncestry.rend(); ++it)
523
{
524
if (back >= 0 && back < fabricatedAncestry.size() && (*it)->classIndex == fabricatedAncestry[back]->classIndex)
525
fabricatedAncestry[back] = *it;
526
back--;
527
}
528
529
if (nearestStatement == nullptr)
530
nearestStatement = p.root;
531
fragmentResult.root = p.root;
532
fragmentResult.ancestry = std::move(fabricatedAncestry);
533
fragmentResult.nearestStatement = nearestStatement;
534
fragmentResult.commentLocations = std::move(p.commentLocations);
535
fragmentResult.scopePos = result.parentBlock->location.begin;
536
return fragmentResult;
537
}
538
539
struct UsageFinder : public AstVisitor
540
{
541
542
explicit UsageFinder(NotNull<DataFlowGraph> dfg)
543
: dfg(dfg)
544
{
545
// We explicitly suggest that the usage finder propulate types for instance and enum by default
546
// These are common enough types that sticking them in the environment is a good idea
547
// and it lets magic functions work correctly too.
548
referencedBindings.emplace_back("Instance");
549
referencedBindings.emplace_back("Enum");
550
}
551
552
bool visit(AstExprConstantString* expr) override
553
{
554
// Populating strings in the referenced bindings is nice too, because it means that magic functions that look
555
// up types by names will work correctly too.
556
// Only if the actual type alias exists will we populate it over, otherwise, the strings will just get ignored
557
referencedBindings.emplace_back(expr->value.data, expr->value.size);
558
return true;
559
}
560
561
bool visit(AstType* node) override
562
{
563
return true;
564
}
565
566
bool visit(AstTypePack* node) override
567
{
568
return true;
569
}
570
571
bool visit(AstStatTypeAlias* alias) override
572
{
573
declaredAliases.insert(std::string(alias->name.value));
574
return true;
575
}
576
577
bool visit(AstTypeReference* ref) override
578
{
579
if (std::optional<AstName> prefix = ref->prefix)
580
referencedImportedBindings.emplace_back(prefix->value, ref->name.value);
581
else
582
referencedBindings.emplace_back(ref->name.value);
583
584
return true;
585
}
586
587
bool visit(AstExpr* expr) override
588
{
589
if (auto opt = dfg->getDefOptional(expr))
590
mentionedDefs.insert(opt->get());
591
if (auto ref = dfg->getRefinementKey(expr))
592
mentionedDefs.insert(ref->def);
593
if (auto local = expr->as<AstExprLocal>())
594
{
595
auto def = dfg->getDef(local);
596
localBindingsReferenced.emplace_back(def, local->local);
597
symbolsToRefine.emplace_back(def, Symbol(local->local));
598
}
599
return true;
600
}
601
602
bool visit(AstExprGlobal* global) override
603
{
604
globalDefsToPrePopulate.emplace_back(global->name, dfg->getDef(global));
605
auto def = dfg->getDef(global);
606
symbolsToRefine.emplace_back(def, Symbol(global->name));
607
return true;
608
}
609
610
bool visit(AstStatFunction* function) override
611
{
612
if (AstExprGlobal* g = function->name->as<AstExprGlobal>())
613
globalFunctionsReferenced.emplace_back(g->name);
614
615
return true;
616
}
617
618
NotNull<DataFlowGraph> dfg;
619
DenseHashSet<Name> declaredAliases{""};
620
std::vector<std::pair<const Def*, AstLocal*>> localBindingsReferenced;
621
DenseHashSet<const Def*> mentionedDefs{nullptr};
622
std::vector<Name> referencedBindings{""};
623
std::vector<std::pair<Name, Name>> referencedImportedBindings{{"", ""}};
624
std::vector<std::pair<AstName, const Def*>> globalDefsToPrePopulate;
625
std::vector<AstName> globalFunctionsReferenced;
626
std::vector<std::pair<const Def*, Symbol>> symbolsToRefine;
627
};
628
629
// Runs the `UsageFinder` traversal on the fragment and grabs all of the types that are
630
// referenced in the fragment. We'll clone these and place them in the appropriate spots
631
// in the scope so that they are available during typechecking.
632
void cloneTypesFromFragment(
633
CloneState& cloneState,
634
const Scope* staleScope,
635
const ModulePtr& staleModule,
636
NotNull<TypeArena> destArena,
637
NotNull<DataFlowGraph> dfg,
638
NotNull<BuiltinTypes> builtins,
639
AstStatBlock* program,
640
Scope* destScope
641
)
642
{
643
LUAU_TIMETRACE_SCOPE("Luau::cloneTypesFromFragment", "FragmentAutocomplete");
644
645
UsageFinder f{dfg};
646
program->visit(&f);
647
// These are defs that have been mentioned. find the appropriate lvalue type and rvalue types and place them in the scope
648
// First - any locals that have been mentioned in the fragment need to be placed in the bindings and lvalueTypes sections.
649
650
for (const auto& d : f.mentionedDefs)
651
{
652
if (std::optional<TypeId> rValueRefinement = staleScope->lookupRValueRefinementType(NotNull{d}))
653
{
654
destScope->rvalueRefinements[d] = Luau::cloneIncremental(*rValueRefinement, *destArena, cloneState, destScope);
655
}
656
657
if (std::optional<TypeId> lValue = staleScope->lookupUnrefinedType(NotNull{d}))
658
{
659
destScope->lvalueTypes[d] = Luau::cloneIncremental(*lValue, *destArena, cloneState, destScope);
660
}
661
}
662
663
for (const auto& [d, loc] : f.localBindingsReferenced)
664
{
665
if (std::optional<std::pair<Symbol, Binding>> pair = staleScope->linearSearchForBindingPair(loc->name.value, true))
666
{
667
destScope->lvalueTypes[d] = Luau::cloneIncremental(pair->second.typeId, *destArena, cloneState, destScope);
668
destScope->bindings[pair->first] = Luau::cloneIncremental(pair->second, *destArena, cloneState, destScope);
669
}
670
}
671
672
for (const auto& [d, syms] : f.symbolsToRefine)
673
{
674
for (const Scope* stale = staleScope; stale; stale = stale->parent.get())
675
{
676
if (auto res = stale->refinements.find(syms); res != stale->refinements.end())
677
{
678
destScope->rvalueRefinements[d] = Luau::cloneIncremental(res->second, *destArena, cloneState, destScope);
679
// If we've found a refinement, just break, otherwise we might end up doing the wrong thing for:
680
//
681
// type TaggedUnion = { tag: 'a', value: number } | { tag: 'b', value: string }
682
// local function foobar(obj: TaggedUnion?)
683
// if obj then
684
// if obj.tag == 'a'
685
// obj.| -- We want the most "narrow" refinement here.
686
// end
687
// end
688
// end
689
//
690
// We could find another binding for `syms` and then set _that_.
691
break;
692
}
693
}
694
}
695
696
// Second - any referenced type alias bindings need to be placed in scope so type annotation can be resolved.
697
// If the actual type alias appears in the fragment on the lhs as a definition (in declaredAliases), it will be processed during typechecking
698
// anyway
699
for (const auto& x : f.referencedBindings)
700
{
701
if (f.declaredAliases.contains(x))
702
continue;
703
if (std::optional<TypeFun> tf = staleScope->lookupType(x))
704
{
705
destScope->privateTypeBindings[x] = Luau::cloneIncremental(*tf, *destArena, cloneState, destScope);
706
}
707
}
708
709
// Third - any referenced imported type bindings need to be imported in
710
for (const auto& [mod, name] : f.referencedImportedBindings)
711
{
712
if (std::optional<TypeFun> tf = staleScope->lookupImportedType(mod, name))
713
{
714
destScope->importedTypeBindings[mod].insert_or_assign(name, Luau::cloneIncremental(*tf, *destArena, cloneState, destScope));
715
}
716
}
717
718
// Fourth - prepopulate the global function types
719
for (const auto& name : f.globalFunctionsReferenced)
720
{
721
if (auto ty = staleModule->getModuleScope()->lookup(name))
722
{
723
destScope->bindings[name] = Binding{Luau::cloneIncremental(*ty, *destArena, cloneState, destScope)};
724
}
725
else
726
{
727
TypeId bt = destArena->addType(BlockedType{});
728
destScope->bindings[name] = Binding{bt};
729
}
730
}
731
732
// Fifth - prepopulate the globals here
733
for (const auto& [name, def] : f.globalDefsToPrePopulate)
734
{
735
if (auto ty = staleModule->getModuleScope()->lookup(name))
736
{
737
destScope->lvalueTypes[def] = Luau::cloneIncremental(*ty, *destArena, cloneState, destScope);
738
}
739
else if (auto ty = destScope->lookup(name))
740
{
741
// This branch is a little strange - we are looking up a symbol in the destScope
742
// This scope has no parent pointer, and only cloned types are written to it, so this is a
743
// safe operation to do without cloning.
744
// The reason we do this, is the usage finder will traverse the global functions referenced first
745
// If there is no name associated with this function at the global scope, it must appear first in the fragment and we must
746
// create a blocked type for it. We write this blocked type directly into the `destScope` bindings
747
// Then when we go to traverse the `AstExprGlobal` associated with this function, we need to ensure that we map the def -> blockedType
748
// in `lvalueTypes`, which was previously written into `destScope`
749
destScope->lvalueTypes[def] = *ty;
750
}
751
}
752
753
// Finally, clone the returnType on the staleScope. This helps avoid potential leaks of free types.
754
if (staleScope->returnType)
755
destScope->returnType = Luau::cloneIncremental(staleScope->returnType, *destArena, cloneState, destScope);
756
}
757
758
static FrontendModuleResolver& getModuleResolver(Frontend& frontend, std::optional<FrontendOptions> options)
759
{
760
if ((frontend.getLuauSolverMode() == SolverMode::New) || !options)
761
return frontend.moduleResolver;
762
763
return options->forAutocomplete ? frontend.moduleResolverForAutocomplete : frontend.moduleResolver;
764
}
765
766
bool statIsBeforePos(const AstNode* stat, const Position& cursorPos)
767
{
768
return (stat->location.begin < cursorPos);
769
}
770
771
FragmentAutocompleteAncestryResult findAncestryForFragmentParse_DEPRECATED(AstStatBlock* root, const Position& cursorPos)
772
{
773
std::vector<AstNode*> ancestry = findAncestryAtPositionForAutocomplete(root, cursorPos);
774
// Should always contain the root AstStat
775
LUAU_ASSERT(ancestry.size() >= 1);
776
DenseHashMap<AstName, AstLocal*> localMap{AstName()};
777
std::vector<AstLocal*> localStack;
778
AstStat* nearestStatement = nullptr;
779
for (AstNode* node : ancestry)
780
{
781
if (auto block = node->as<AstStatBlock>())
782
{
783
for (auto stat : block->body)
784
{
785
if (stat->location.begin <= cursorPos)
786
nearestStatement = stat;
787
}
788
}
789
}
790
if (!nearestStatement)
791
nearestStatement = ancestry[0]->asStat();
792
LUAU_ASSERT(nearestStatement);
793
794
for (AstNode* node : ancestry)
795
{
796
if (auto block = node->as<AstStatBlock>())
797
{
798
for (auto stat : block->body)
799
{
800
if (statIsBeforePos(stat, nearestStatement->location.begin))
801
{
802
// This statement precedes the current one
803
if (auto statLoc = stat->as<AstStatLocal>())
804
{
805
for (auto v : statLoc->vars)
806
{
807
localStack.push_back(v);
808
localMap[v->name] = v;
809
}
810
}
811
else if (auto locFun = stat->as<AstStatLocalFunction>())
812
{
813
localStack.push_back(locFun->name);
814
localMap[locFun->name->name] = locFun->name;
815
if (locFun->location.contains(cursorPos))
816
{
817
for (AstLocal* loc : locFun->func->args)
818
{
819
localStack.push_back(loc);
820
localMap[loc->name] = loc;
821
}
822
}
823
}
824
else if (auto globFun = stat->as<AstStatFunction>())
825
{
826
if (globFun->location.contains(cursorPos))
827
{
828
for (AstLocal* loc : globFun->func->args)
829
{
830
localStack.push_back(loc);
831
localMap[loc->name] = loc;
832
}
833
}
834
}
835
else if (auto typeFun = stat->as<AstStatTypeFunction>())
836
{
837
if (typeFun->location.contains(cursorPos))
838
{
839
for (AstLocal* loc : typeFun->body->args)
840
{
841
localStack.push_back(loc);
842
localMap[loc->name] = loc;
843
}
844
}
845
}
846
}
847
}
848
}
849
if (auto exprFunc = node->as<AstExprFunction>())
850
{
851
if (exprFunc->location.contains(cursorPos))
852
{
853
for (auto v : exprFunc->args)
854
{
855
localStack.push_back(v);
856
localMap[v->name] = v;
857
}
858
}
859
}
860
}
861
862
return {std::move(localMap), std::move(localStack), std::move(ancestry), std::move(nearestStatement)};
863
}
864
865
/**
866
* Get document offsets is a function that takes a source text document as well as a start position and end position(line, column) in that
867
* document and attempts to get the concrete text between those points. It returns a pair of:
868
* - start offset that represents an index in the source `char*` corresponding to startPos
869
* - length, that represents how many more bytes to read to get to endPos.
870
* Example - your document is "foo bar baz" and getDocumentOffsets is passed (0, 4), (0, 8). This function returns the pair {3, 5}
871
* which corresponds to the string " bar "
872
*/
873
static std::pair<size_t, size_t> getDocumentOffsets(std::string_view src, const Position& startPos, const Position& endPos)
874
{
875
size_t lineCount = 0;
876
size_t colCount = 0;
877
878
size_t docOffset = 0;
879
size_t startOffset = 0;
880
size_t endOffset = 0;
881
bool foundStart = false;
882
bool foundEnd = false;
883
884
for (char c : src)
885
{
886
if (foundStart && foundEnd)
887
break;
888
889
if (startPos.line == lineCount && startPos.column == colCount)
890
{
891
foundStart = true;
892
startOffset = docOffset;
893
}
894
895
if (endPos.line == lineCount && endPos.column == colCount)
896
{
897
endOffset = docOffset;
898
foundEnd = true;
899
}
900
901
// We put a cursor position that extends beyond the extents of the current line
902
if (foundStart && !foundEnd && (lineCount > endPos.line))
903
{
904
foundEnd = true;
905
endOffset = docOffset - 1;
906
}
907
908
if (c == '\n')
909
{
910
lineCount++;
911
colCount = 0;
912
}
913
else
914
{
915
colCount++;
916
}
917
docOffset++;
918
}
919
920
if (foundStart && !foundEnd)
921
endOffset = src.length();
922
923
size_t min = std::min(startOffset, endOffset);
924
size_t len = std::max(startOffset, endOffset) - min;
925
return {min, len};
926
}
927
928
ScopePtr findClosestScope_DEPRECATED(const ModulePtr& module, const AstStat* nearestStatement)
929
{
930
LUAU_ASSERT(module->hasModuleScope());
931
932
ScopePtr closest = module->getModuleScope();
933
934
// find the scope the nearest statement belonged to.
935
for (const auto& [loc, sc] : module->scopes)
936
{
937
if (loc.encloses(nearestStatement->location) && closest->location.begin <= loc.begin)
938
closest = sc;
939
}
940
941
return closest;
942
}
943
944
ScopePtr findClosestScope(const ModulePtr& module, const Position& scopePos)
945
{
946
LUAU_ASSERT(module->hasModuleScope());
947
ScopePtr closest = module->getModuleScope();
948
// find the scope the nearest statement belonged to.
949
for (const auto& [loc, sc] : module->scopes)
950
{
951
// We bias towards the later scopes because those correspond to inner scopes.
952
// in the case of if statements, we create two scopes at the same location for the body of the then
953
// and else branches, so we need to bias later. This is why the closest update condition has a <=
954
// instead of a <
955
if (sc->location.contains(scopePos) && closest->location.begin <= sc->location.begin)
956
closest = sc;
957
}
958
return closest;
959
}
960
961
std::optional<FragmentParseResult> parseFragment_DEPRECATED(
962
AstStatBlock* root,
963
AstNameTable* names,
964
std::string_view src,
965
const Position& cursorPos,
966
std::optional<Position> fragmentEndPosition
967
)
968
{
969
FragmentAutocompleteAncestryResult result = findAncestryForFragmentParse_DEPRECATED(root, cursorPos);
970
AstStat* nearestStatement = result.nearestStatement;
971
972
const Location& rootSpan = root->location;
973
// Did we append vs did we insert inline
974
bool appended = cursorPos >= rootSpan.end;
975
// statement spans multiple lines
976
bool multiline = nearestStatement->location.begin.line != nearestStatement->location.end.line;
977
978
const Position endPos = fragmentEndPosition.value_or(cursorPos);
979
980
// We start by re-parsing everything (we'll refine this as we go)
981
Position startPos = root->location.begin;
982
983
// If we added to the end of the sourceModule, use the end of the nearest location
984
if (appended && multiline)
985
startPos = nearestStatement->location.end;
986
// Statement spans one line && cursorPos is either on the same line or after
987
else if (!multiline && cursorPos.line >= nearestStatement->location.end.line)
988
startPos = nearestStatement->location.begin;
989
else if (multiline && nearestStatement->location.end.line < cursorPos.line)
990
startPos = nearestStatement->location.end;
991
else
992
startPos = nearestStatement->location.begin;
993
994
auto [offsetStart, parseLength] = getDocumentOffsets(src, startPos, endPos);
995
const char* srcStart = src.data() + offsetStart;
996
std::string_view dbg = src.substr(offsetStart, parseLength);
997
FragmentParseResult fragmentResult;
998
fragmentResult.fragmentToParse = std::string(dbg.data(), parseLength);
999
// For the duration of the incremental parse, we want to allow the name table to re-use duplicate names
1000
if (FFlag::DebugLogFragmentsFromAutocomplete)
1001
logLuau("Fragment Selected", dbg);
1002
1003
ParseOptions opts;
1004
opts.allowDeclarationSyntax = false;
1005
opts.captureComments = true;
1006
opts.parseFragment = FragmentParseResumeSettings{std::move(result.localMap), std::move(result.localStack), startPos};
1007
ParseResult p = Luau::Parser::parse(srcStart, parseLength, *names, *fragmentResult.alloc, std::move(opts));
1008
// This means we threw a ParseError and we should decline to offer autocomplete here.
1009
if (p.root == nullptr)
1010
return std::nullopt;
1011
1012
std::vector<AstNode*> fabricatedAncestry = std::move(result.ancestry);
1013
1014
// Get the ancestry for the fragment at the offset cursor position.
1015
// Consumers have the option to request with fragment end position, so we cannot just use the end position of our parse result as the
1016
// cursor position. Instead, use the cursor position calculated as an offset from our start position.
1017
std::vector<AstNode*> fragmentAncestry = findAncestryAtPositionForAutocomplete(p.root, cursorPos);
1018
fabricatedAncestry.insert(fabricatedAncestry.end(), fragmentAncestry.begin(), fragmentAncestry.end());
1019
if (nearestStatement == nullptr)
1020
nearestStatement = p.root;
1021
fragmentResult.root = std::move(p.root);
1022
fragmentResult.ancestry = std::move(fabricatedAncestry);
1023
fragmentResult.nearestStatement = nearestStatement;
1024
fragmentResult.commentLocations = std::move(p.commentLocations);
1025
return fragmentResult;
1026
}
1027
1028
static void reportWaypoint(IFragmentAutocompleteReporter* reporter, FragmentAutocompleteWaypoint type)
1029
{
1030
if (!reporter)
1031
return;
1032
1033
reporter->reportWaypoint(type);
1034
}
1035
1036
static void reportFragmentString(IFragmentAutocompleteReporter* reporter, std::string_view fragment)
1037
{
1038
if (!reporter)
1039
return;
1040
1041
reporter->reportFragmentString(fragment);
1042
}
1043
1044
struct ScopedExit
1045
{
1046
public:
1047
explicit ScopedExit(std::function<void()> f)
1048
: func(std::move(f))
1049
{
1050
LUAU_ASSERT(func);
1051
}
1052
1053
ScopedExit(const ScopedExit&) = delete;
1054
ScopedExit& operator=(const ScopedExit&) = delete;
1055
ScopedExit() = default;
1056
ScopedExit(ScopedExit&& other) noexcept
1057
: ScopedExit()
1058
{
1059
std::swap(func, other.func);
1060
}
1061
1062
ScopedExit& operator=(ScopedExit&& other) noexcept
1063
{
1064
ScopedExit temp(std::move(other));
1065
std::swap(func, temp.func);
1066
return *this;
1067
}
1068
1069
~ScopedExit()
1070
{
1071
if (func)
1072
func();
1073
}
1074
1075
private:
1076
std::function<void()> func;
1077
};
1078
1079
1080
FragmentTypeCheckResult typecheckFragment_(
1081
Frontend& frontend,
1082
AstStatBlock* root,
1083
const ModulePtr& stale,
1084
const ScopePtr& closestScope,
1085
const Position& cursorPos,
1086
std::unique_ptr<Allocator> astAllocator,
1087
const FrontendOptions& opts,
1088
IFragmentAutocompleteReporter* reporter
1089
)
1090
{
1091
LUAU_TIMETRACE_SCOPE("Luau::typecheckFragment_", "FragmentAutocomplete");
1092
freeze(stale->internalTypes);
1093
freeze(stale->interfaceTypes);
1094
ModulePtr incrementalModule = std::make_shared<Module>();
1095
incrementalModule->name = stale->name;
1096
incrementalModule->humanReadableName = "Incremental$" + stale->humanReadableName;
1097
incrementalModule->internalTypes.owningModule = incrementalModule.get();
1098
incrementalModule->interfaceTypes.owningModule = incrementalModule.get();
1099
incrementalModule->allocator = std::move(astAllocator);
1100
incrementalModule->checkedInNewSolver = true;
1101
unfreeze(incrementalModule->internalTypes);
1102
unfreeze(incrementalModule->interfaceTypes);
1103
1104
/// Setup typecheck limits
1105
TypeCheckLimits limits;
1106
if (opts.moduleTimeLimitSec)
1107
limits.finishTime = TimeTrace::getClock() + *opts.moduleTimeLimitSec;
1108
else
1109
limits.finishTime = std::nullopt;
1110
limits.cancellationToken = opts.cancellationToken;
1111
1112
/// Icehandler
1113
NotNull<InternalErrorReporter> iceHandler{&frontend.iceHandler};
1114
/// Make the shared state for the unifier (recursion + iteration limits)
1115
UnifierSharedState unifierState{iceHandler};
1116
unifierState.counters.recursionLimit = FInt::LuauTypeInferRecursionLimit;
1117
unifierState.counters.iterationLimit = limits.unifierIterationLimit.value_or(FInt::LuauTypeInferIterationLimit);
1118
1119
/// Initialize the normalizer
1120
Normalizer normalizer{&incrementalModule->internalTypes, frontend.builtinTypes, NotNull{&unifierState}, SolverMode::New};
1121
1122
/// User defined type functions runtime
1123
TypeFunctionRuntime typeFunctionRuntime(iceHandler, NotNull{&limits});
1124
1125
typeFunctionRuntime.allowEvaluation = false;
1126
1127
/// Create a DataFlowGraph just for the surrounding context
1128
DataFlowGraph dfg = DataFlowGraphBuilder::build(root, NotNull{&incrementalModule->defArena}, NotNull{&incrementalModule->keyArena}, iceHandler);
1129
reportWaypoint(reporter, FragmentAutocompleteWaypoint::DfgBuildEnd);
1130
1131
// IncrementalModule gets moved at the end of the function, so capturing it here will cause SIGSEGV.
1132
// We'll capture just the name instead, since that's all we need to clean up the requireTrace at the end
1133
ScopedExit scopedExit{[&, name = incrementalModule->name]()
1134
{
1135
frontend.requireTrace.erase(name);
1136
}};
1137
1138
frontend.requireTrace[incrementalModule->name] = traceRequires(frontend.fileResolver, root, incrementalModule->name, limits);
1139
1140
1141
FrontendModuleResolver& resolver = getModuleResolver(frontend, opts);
1142
std::shared_ptr<Scope> freshChildOfNearestScope = std::make_shared<Scope>(nullptr);
1143
/// Contraint Generator
1144
ConstraintGenerator cg{
1145
incrementalModule,
1146
NotNull{&normalizer},
1147
NotNull{&typeFunctionRuntime},
1148
NotNull{&resolver},
1149
frontend.builtinTypes,
1150
iceHandler,
1151
freshChildOfNearestScope,
1152
frontend.globals.globalTypeFunctionScope,
1153
nullptr,
1154
nullptr,
1155
NotNull{&dfg},
1156
{}
1157
};
1158
1159
CloneState cloneState{frontend.builtinTypes};
1160
incrementalModule->scopes.emplace_back(root->location, freshChildOfNearestScope);
1161
freshChildOfNearestScope->interiorFreeTypes.emplace();
1162
freshChildOfNearestScope->interiorFreeTypePacks.emplace();
1163
cg.rootScope = freshChildOfNearestScope.get();
1164
1165
// Create module-local scope for the type function environment
1166
ScopePtr localTypeFunctionScope = std::make_shared<Scope>(cg.typeFunctionScope);
1167
localTypeFunctionScope->location = root->location;
1168
cg.typeFunctionRuntime->rootScope = localTypeFunctionScope;
1169
1170
reportWaypoint(reporter, FragmentAutocompleteWaypoint::CloneAndSquashScopeStart);
1171
cloneTypesFromFragment(
1172
cloneState,
1173
closestScope.get(),
1174
stale,
1175
NotNull{&incrementalModule->internalTypes},
1176
NotNull{&dfg},
1177
frontend.builtinTypes,
1178
root,
1179
freshChildOfNearestScope.get()
1180
);
1181
reportWaypoint(reporter, FragmentAutocompleteWaypoint::CloneAndSquashScopeEnd);
1182
1183
cg.visitFragmentRoot(freshChildOfNearestScope, root);
1184
1185
for (auto p : cg.scopes)
1186
incrementalModule->scopes.emplace_back(std::move(p));
1187
1188
1189
reportWaypoint(reporter, FragmentAutocompleteWaypoint::ConstraintSolverStart);
1190
1191
/// Initialize the constraint solver and run it
1192
ConstraintSolver cs{
1193
NotNull{&normalizer},
1194
NotNull{&typeFunctionRuntime},
1195
NotNull(cg.rootScope),
1196
borrowConstraints(cg.constraints),
1197
NotNull{&cg.scopeToFunction},
1198
incrementalModule,
1199
NotNull{&resolver},
1200
{},
1201
nullptr,
1202
NotNull{&dfg},
1203
std::move(limits)
1204
};
1205
1206
try
1207
{
1208
cs.run();
1209
}
1210
catch (const TimeLimitError&)
1211
{
1212
stale->timeout = true;
1213
}
1214
catch (const UserCancelError&)
1215
{
1216
stale->cancelled = true;
1217
}
1218
1219
reportWaypoint(reporter, FragmentAutocompleteWaypoint::ConstraintSolverEnd);
1220
1221
if (FFlag::LuauOverloadGetsInstantiated)
1222
{
1223
ExpectedTypeVisitor etv{
1224
NotNull{&incrementalModule->astTypes},
1225
NotNull{&incrementalModule->astExpectedTypes},
1226
NotNull{&incrementalModule->astResolvedTypes},
1227
NotNull{&incrementalModule->astOverloadResolvedTypes},
1228
NotNull{&incrementalModule->internalTypes},
1229
frontend.builtinTypes,
1230
NotNull{freshChildOfNearestScope.get()}
1231
};
1232
root->visit(&etv);
1233
}
1234
else
1235
{
1236
ExpectedTypeVisitor etv{
1237
NotNull{&incrementalModule->astTypes},
1238
NotNull{&incrementalModule->astExpectedTypes},
1239
NotNull{&incrementalModule->astResolvedTypes},
1240
NotNull{&incrementalModule->internalTypes},
1241
frontend.builtinTypes,
1242
NotNull{freshChildOfNearestScope.get()}
1243
};
1244
root->visit(&etv);
1245
}
1246
1247
1248
// In frontend we would forbid internal types
1249
// because this is just for autocomplete, we don't actually care
1250
// We also don't even need to typecheck - just synthesize types as best as we can
1251
freeze(incrementalModule->internalTypes);
1252
freeze(incrementalModule->interfaceTypes);
1253
freshChildOfNearestScope->parent = closestScope;
1254
return {std::move(incrementalModule), std::move(freshChildOfNearestScope)};
1255
}
1256
1257
std::pair<FragmentTypeCheckStatus, FragmentTypeCheckResult> typecheckFragment(
1258
Frontend& frontend,
1259
const ModuleName& moduleName,
1260
const Position& cursorPos,
1261
std::optional<FrontendOptions> opts,
1262
std::string_view src,
1263
std::optional<Position> fragmentEndPosition,
1264
AstStatBlock* recentParse,
1265
IFragmentAutocompleteReporter* reporter
1266
)
1267
{
1268
LUAU_TIMETRACE_SCOPE("Luau::typecheckFragment", "FragmentAutocomplete");
1269
LUAU_TIMETRACE_ARGUMENT("name", moduleName.c_str());
1270
1271
if (!frontend.allModuleDependenciesValid(moduleName, opts && opts->forAutocomplete))
1272
return {FragmentTypeCheckStatus::SkipAutocomplete, {}};
1273
1274
FrontendModuleResolver& resolver = getModuleResolver(frontend, opts);
1275
ModulePtr module = resolver.getModule(moduleName);
1276
if (!module)
1277
{
1278
LUAU_ASSERT(!"Expected Module for fragment typecheck");
1279
return {};
1280
}
1281
1282
std::optional<FragmentParseResult> tryParse;
1283
tryParse = parseFragment(module->root, recentParse, module->names.get(), src, cursorPos, fragmentEndPosition);
1284
1285
1286
if (!tryParse)
1287
return {FragmentTypeCheckStatus::SkipAutocomplete, {}};
1288
1289
FragmentParseResult& parseResult = *tryParse;
1290
1291
if (isWithinComment(parseResult.commentLocations, fragmentEndPosition.value_or(cursorPos)))
1292
return {FragmentTypeCheckStatus::SkipAutocomplete, {}};
1293
1294
FrontendOptions frontendOptions = opts.value_or(frontend.options);
1295
const ScopePtr& closestScope = findClosestScope(module, parseResult.scopePos);
1296
FragmentTypeCheckResult result =
1297
typecheckFragment_(frontend, parseResult.root, module, closestScope, cursorPos, std::move(parseResult.alloc), frontendOptions, reporter);
1298
result.ancestry = std::move(parseResult.ancestry);
1299
reportFragmentString(reporter, tryParse->fragmentToParse);
1300
return {FragmentTypeCheckStatus::Success, result};
1301
}
1302
1303
FragmentAutocompleteStatusResult tryFragmentAutocomplete(
1304
Frontend& frontend,
1305
const ModuleName& moduleName,
1306
Position cursorPosition,
1307
FragmentContext context,
1308
StringCompletionCallback stringCompletionCB
1309
)
1310
{
1311
bool isInHotComment = isWithinHotComment(context.freshParse.hotcomments, cursorPosition);
1312
if (isWithinComment(context.freshParse.commentLocations, cursorPosition) && !isInHotComment)
1313
return {FragmentAutocompleteStatus::Success, std::nullopt};
1314
// TODO: we should calculate fragmentEnd position here, by using context.newAstRoot and cursorPosition
1315
try
1316
{
1317
Luau::FragmentAutocompleteResult fragmentAutocomplete = Luau::fragmentAutocomplete(
1318
frontend,
1319
context.newSrc,
1320
moduleName,
1321
cursorPosition,
1322
context.opts,
1323
std::move(stringCompletionCB),
1324
context.DEPRECATED_fragmentEndPosition,
1325
context.freshParse.root,
1326
context.reporter,
1327
isInHotComment
1328
);
1329
return {FragmentAutocompleteStatus::Success, std::move(fragmentAutocomplete)};
1330
}
1331
catch (const Luau::InternalCompilerError& e)
1332
{
1333
if (FFlag::DebugLogFragmentsFromAutocomplete)
1334
logLuau("tryFragmentAutocomplete exception", e.what());
1335
return {FragmentAutocompleteStatus::InternalIce, std::nullopt};
1336
}
1337
}
1338
1339
FragmentAutocompleteResult fragmentAutocomplete(
1340
Frontend& frontend,
1341
std::string_view src,
1342
const ModuleName& moduleName,
1343
Position cursorPosition,
1344
std::optional<FrontendOptions> opts,
1345
StringCompletionCallback callback,
1346
std::optional<Position> fragmentEndPosition,
1347
AstStatBlock* recentParse,
1348
IFragmentAutocompleteReporter* reporter,
1349
bool isInHotComment
1350
)
1351
{
1352
LUAU_TIMETRACE_SCOPE("Luau::fragmentAutocomplete", "FragmentAutocomplete");
1353
LUAU_TIMETRACE_ARGUMENT("name", moduleName.c_str());
1354
1355
auto [tcStatus, tcResult] = typecheckFragment(frontend, moduleName, cursorPosition, opts, src, fragmentEndPosition, recentParse, reporter);
1356
if (tcStatus == FragmentTypeCheckStatus::SkipAutocomplete)
1357
return {};
1358
1359
reportWaypoint(reporter, FragmentAutocompleteWaypoint::TypecheckFragmentEnd);
1360
auto globalScope = (opts && opts->forAutocomplete) ? frontend.globalsForAutocomplete.globalScope.get() : frontend.globals.globalScope.get();
1361
if (FFlag::DebugLogFragmentsFromAutocomplete)
1362
logLuau("Fragment Autocomplete Source Script", src);
1363
unfreeze(tcResult.incrementalModule->internalTypes);
1364
auto result = Luau::autocomplete_(
1365
tcResult.incrementalModule,
1366
frontend.builtinTypes,
1367
&tcResult.incrementalModule->internalTypes,
1368
tcResult.ancestry,
1369
globalScope,
1370
tcResult.freshScope,
1371
cursorPosition,
1372
frontend.fileResolver,
1373
std::move(callback),
1374
isInHotComment
1375
);
1376
freeze(tcResult.incrementalModule->internalTypes);
1377
reportWaypoint(reporter, FragmentAutocompleteWaypoint::AutocompleteEnd);
1378
return {std::move(tcResult.incrementalModule), tcResult.freshScope.get(), std::move(result)};
1379
}
1380
1381
} // namespace Luau
1382
1383