Skip to content

Commit 775b3a4

Browse files
committed
Implement topological sort for const initializers
1 parent d662433 commit 775b3a4

File tree

2 files changed

+199
-29
lines changed

2 files changed

+199
-29
lines changed

lib/src/eval/compiler/compiler.dart

Lines changed: 155 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import 'package:analyzer/dart/ast/ast.dart';
2+
import 'package:analyzer/dart/ast/visitor.dart';
23
import 'package:collection/collection.dart';
34
import 'package:dart_eval/dart_eval_bridge.dart';
45
import 'package:dart_eval/src/eval/compiler/builtins.dart';
@@ -446,45 +447,135 @@ class Compiler implements BridgeDeclarationRegistry, EvalPluginRegistry {
446447
_ctx.topLevelGlobalIndices = _topLevelGlobalIndices;
447448

448449
try {
449-
/// Compile statics first so we can infer their type
450-
_topLevelDeclarationsMap.forEach((key, value) {
451-
final visibleInLibrary = visibleDeclarationsByIndex[key];
452-
if (visibleInLibrary == null) {
453-
return;
454-
}
455-
value.forEach((name, tlDeclaration) {
456-
if (tlDeclaration.isBridge || !visibleInLibrary.containsKey(name)) {
457-
return;
458-
}
459-
final declaration = tlDeclaration.declaration!;
460-
_ctx.library = key;
450+
// Keep track of declarations we've already compiled to avoid processing them in the final pass.
451+
final alreadyCompiled = <AstNode>{};
452+
453+
// We iterate once to gather consts for sorting and compile non-const statics immediately.
454+
final constDeclarationsByContainer =
455+
<AstNode, List<_ConstDeclarationInfo>>{};
456+
457+
_topLevelDeclarationsMap.forEach((libraryIndex, declarations) {
458+
declarations.forEach((name, declOrBridge) {
459+
if (declOrBridge.isBridge) return;
460+
final declaration = declOrBridge.declaration!;
461+
462+
// Handle top-level variables
461463
if (declaration is VariableDeclaration &&
462-
declaration.parent!.parent is TopLevelVariableDeclaration) {
463-
compileDeclaration(declaration, _ctx);
464-
_ctx.resetStack();
465-
} else if (declaration is ClassDeclaration) {
466-
_ctx.currentClass = declaration;
467-
for (final d in declaration.members
468-
.whereType<FieldDeclaration>()
469-
.where((e) => e.isStatic)) {
470-
compileFieldDeclaration(-1, d, _ctx, declaration);
464+
declaration.parent?.parent is TopLevelVariableDeclaration) {
465+
final topLevelVarDecl =
466+
declaration.parent!.parent as TopLevelVariableDeclaration;
467+
if (topLevelVarDecl.variables.isConst) {
468+
(constDeclarationsByContainer[topLevelVarDecl] ??= []).add(
469+
_ConstDeclarationInfo(
470+
declaration, topLevelVarDecl, declOrBridge.sourceLib));
471+
} else {
472+
_ctx.library = declOrBridge.sourceLib;
473+
compileDeclaration(declaration, _ctx);
474+
alreadyCompiled.add(declaration);
471475
_ctx.resetStack();
472476
}
473-
_ctx.currentClass = null;
474-
} else if (declaration is EnumDeclaration) {
477+
} else if (declaration is ClassDeclaration) {
475478
_ctx.currentClass = declaration;
476-
for (final d in declaration.members
477-
.whereType<FieldDeclaration>()
478-
.where((e) => e.isStatic)) {
479-
compileFieldDeclaration(-1, d, _ctx, declaration);
480-
_ctx.resetStack();
479+
for (final member
480+
in declaration.members.whereType<FieldDeclaration>()) {
481+
if (member.isStatic) {
482+
if (member.fields.isConst) {
483+
for (final fieldVar in member.fields.variables) {
484+
final fqName =
485+
'${declaration.name.lexeme}.${fieldVar.name.lexeme}';
486+
final fieldDeclOrBridge =
487+
_topLevelDeclarationsMap[libraryIndex]![fqName];
488+
if (fieldDeclOrBridge != null) {
489+
(constDeclarationsByContainer[declaration] ??= []).add(
490+
_ConstDeclarationInfo(fieldVar, declaration,
491+
fieldDeclOrBridge.sourceLib));
492+
}
493+
}
494+
} else {
495+
_ctx.library = libraryIndex;
496+
compileFieldDeclaration(-1, member, _ctx, declaration);
497+
alreadyCompiled.add(member);
498+
_ctx.resetStack();
499+
}
500+
}
481501
}
482502
_ctx.currentClass = null;
483503
}
484504
});
485505
});
486506

487-
/// Compile the rest of the declarations
507+
constDeclarationsByContainer.forEach((container, constDecls) {
508+
if (constDecls.isEmpty) return;
509+
510+
_ctx.library = constDecls.first.libraryIndex;
511+
512+
final fieldMap = {for (var d in constDecls) d.name: d};
513+
final fieldNames = fieldMap.keys.toSet();
514+
final Map<String, Set<String>> adjList = {
515+
for (var name in fieldNames) name: {}
516+
};
517+
final Map<String, int> inDegrees = {
518+
for (var name in fieldNames) name: 0
519+
};
520+
521+
// Build dependency graph
522+
for (final constDeclInfo in constDecls) {
523+
final dependerName = constDeclInfo.name;
524+
final initializer = constDeclInfo.variable.initializer;
525+
if (initializer != null) {
526+
final dependencyVisitor = _DependencyVisitor(fieldNames);
527+
initializer.visitChildren(dependencyVisitor);
528+
for (final dependencyName in dependencyVisitor.dependencies) {
529+
if (adjList[dependencyName]!.add(dependerName)) {
530+
inDegrees[dependerName] = (inDegrees[dependerName] ?? 0) + 1;
531+
}
532+
}
533+
}
534+
}
535+
536+
// Perform topological sort
537+
final sortedOrder = <_ConstDeclarationInfo>[];
538+
final queue = <String>[];
539+
fieldNames.where((name) => inDegrees[name] == 0).forEach(queue.add);
540+
541+
while (queue.isNotEmpty) {
542+
final currentName = queue.removeAt(0);
543+
sortedOrder.add(fieldMap[currentName]!);
544+
for (final neighborName in adjList[currentName]!) {
545+
inDegrees[neighborName] = inDegrees[neighborName]! - 1;
546+
if (inDegrees[neighborName] == 0) {
547+
queue.add(neighborName);
548+
}
549+
}
550+
}
551+
552+
if (sortedOrder.length < constDecls.length) {
553+
final containerName = container is NamedCompilationUnitMember
554+
? '"${container.name.lexeme}"'
555+
: 'the library';
556+
throw CompileError(
557+
'Circular dependency detected in const declarations in $containerName.');
558+
}
559+
560+
// Compile sorted consts
561+
for (final declInfo in sortedOrder) {
562+
final parentNode = declInfo.variable.parent!.parent!;
563+
if (parentNode is TopLevelVariableDeclaration) {
564+
compileDeclaration(declInfo.variable, _ctx);
565+
alreadyCompiled.add(declInfo.variable);
566+
} else if (parentNode is FieldDeclaration &&
567+
declInfo.parent is ClassDeclaration) {
568+
_ctx.currentClass = declInfo.parent as ClassDeclaration;
569+
compileFieldDeclaration(
570+
-1, parentNode, _ctx, declInfo.parent as ClassDeclaration);
571+
_ctx.currentClass = null;
572+
alreadyCompiled.add(parentNode);
573+
}
574+
_ctx.resetStack();
575+
}
576+
});
577+
578+
// Compile non-statics
488579
_topLevelDeclarationsMap.forEach((key, value) {
489580
_ctx.topLevelDeclarationPositions[key] = {};
490581
_ctx.instanceDeclarationPositions[key] = {};
@@ -498,11 +589,18 @@ class Compiler implements BridgeDeclarationRegistry, EvalPluginRegistry {
498589
return;
499590
}
500591
final declaration = tlDeclaration.declaration!;
592+
593+
// We've already compiled all variable declarations that are top-level or static fields.
594+
// This pass is for compiling the bodies of classes and functions.
501595
if (declaration is ConstructorDeclaration ||
502596
declaration is MethodDeclaration ||
503597
declaration is VariableDeclaration) {
598+
// This skips top-level vars that were already compiled.
504599
return;
505600
}
601+
602+
if (alreadyCompiled.contains(declaration)) return;
603+
506604
_ctx.library = key;
507605
compileDeclaration(declaration, _ctx);
508606
_ctx.resetStack();
@@ -1216,3 +1314,31 @@ class _Import {
12161314
base.resolveUri(uri), import.prefix?.name, import.combinators);
12171315
}
12181316
}
1317+
1318+
class _ConstDeclarationInfo {
1319+
_ConstDeclarationInfo(this.variable, this.parent, this.libraryIndex);
1320+
1321+
final VariableDeclaration variable;
1322+
1323+
final AstNode parent;
1324+
1325+
final int libraryIndex;
1326+
1327+
String get name => variable.name.lexeme;
1328+
}
1329+
1330+
class _DependencyVisitor extends RecursiveAstVisitor<void> {
1331+
_DependencyVisitor(this.availableNames);
1332+
1333+
final Set<String> availableNames;
1334+
1335+
final Set<String> dependencies = {};
1336+
1337+
@override
1338+
void visitSimpleIdentifier(SimpleIdentifier node) {
1339+
if (availableNames.contains(node.name)) {
1340+
dependencies.add(node.name);
1341+
}
1342+
super.visitSimpleIdentifier(node);
1343+
}
1344+
}

test/class_test.dart

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -620,5 +620,49 @@ void main() {
620620
final result = runtime.executeLib('package:example/main.dart', 'main');
621621
expect(result, 42);
622622
});
623+
test('Static const field with forward reference', () {
624+
final runtime = compiler.compileWriteAndLoad({
625+
'example': {
626+
'main.dart': '''
627+
int main() {
628+
return A.c.a;
629+
}
630+
631+
class A {
632+
const A(this.a);
633+
634+
static const A c = A(b * 2);
635+
static const int b = 4;
636+
637+
final int a;
638+
}
639+
''',
640+
}
641+
});
642+
643+
expect(runtime.executeLib('package:example/main.dart', 'main'), 8);
644+
});
645+
test('Static const field with circular dependency should fail', () {
646+
final source ={
647+
'example': {
648+
'main.dart': '''
649+
void main() {
650+
A(1);
651+
}
652+
653+
class A {
654+
const A(this.a);
655+
656+
static const A c = A(b);
657+
static const int b = c.a;
658+
659+
final int a;
660+
}
661+
''',
662+
}
663+
};
664+
expect(() => compiler.compileWriteAndLoad(source),
665+
throwsA(isA<CompileError>()));
666+
});
623667
});
624668
}

0 commit comments

Comments
 (0)