Skip to content

Commit 883a707

Browse files
committed
Implement topological sort for const initializers
1 parent d662433 commit 883a707

File tree

2 files changed

+210
-29
lines changed

2 files changed

+210
-29
lines changed

lib/src/eval/compiler/compiler.dart

Lines changed: 166 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,136 @@ 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+
455+
final constDeclarationsByContainer =
456+
<AstNode, List<_ConstDeclarationInfo>>{};
457+
458+
_topLevelDeclarationsMap.forEach((libraryIndex, declarations) {
459+
declarations.forEach((name, declOrBridge) {
460+
if (declOrBridge.isBridge) return;
461+
final declaration = declOrBridge.declaration!;
462+
463+
// Handle top-level variables
461464
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);
465+
declaration.parent?.parent is TopLevelVariableDeclaration) {
466+
final topLevelVarDecl =
467+
declaration.parent!.parent as TopLevelVariableDeclaration;
468+
if (topLevelVarDecl.variables.isConst) {
469+
(constDeclarationsByContainer[topLevelVarDecl] ??= []).add(
470+
_ConstDeclarationInfo(
471+
declaration, topLevelVarDecl, declOrBridge.sourceLib));
472+
} else {
473+
_ctx.library = declOrBridge.sourceLib;
474+
compileDeclaration(declaration, _ctx);
475+
alreadyCompiled.add(declaration);
471476
_ctx.resetStack();
472477
}
473-
_ctx.currentClass = null;
474-
} else if (declaration is EnumDeclaration) {
478+
} else if (declaration is ClassDeclaration) {
475479
_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();
480+
for (final member
481+
in declaration.members.whereType<FieldDeclaration>()) {
482+
if (member.isStatic) {
483+
if (member.fields.isConst) {
484+
for (final fieldVar in member.fields.variables) {
485+
final fqName =
486+
'${declaration.name.lexeme}.${fieldVar.name.lexeme}';
487+
final fieldDeclOrBridge =
488+
_topLevelDeclarationsMap[libraryIndex]![fqName];
489+
if (fieldDeclOrBridge != null) {
490+
(constDeclarationsByContainer[declaration] ??= []).add(
491+
_ConstDeclarationInfo(fieldVar, declaration,
492+
fieldDeclOrBridge.sourceLib));
493+
}
494+
}
495+
} else {
496+
_ctx.library = libraryIndex;
497+
compileFieldDeclaration(-1, member, _ctx, declaration);
498+
alreadyCompiled.add(member);
499+
_ctx.resetStack();
500+
}
501+
}
481502
}
482503
_ctx.currentClass = null;
483504
}
484505
});
485506
});
486507

487-
/// Compile the rest of the declarations
508+
constDeclarationsByContainer.forEach((container, constDecls) {
509+
if (constDecls.isEmpty) return;
510+
511+
_ctx.library = constDecls.first.libraryIndex;
512+
513+
final fieldMap = {for (var d in constDecls) d.name: d};
514+
final fieldNames = fieldMap.keys.toSet();
515+
final Map<String, Set<String>> adjList = {
516+
for (var name in fieldNames) name: {}
517+
};
518+
final Map<String, int> inDegrees = {
519+
for (var name in fieldNames) name: 0
520+
};
521+
522+
// Build dependency graph
523+
for (final constDeclInfo in constDecls) {
524+
final dependerName = constDeclInfo.name;
525+
final initializer = constDeclInfo.variable.initializer;
526+
if (initializer != null) {
527+
final dependencyVisitor = _DependencyVisitor(fieldNames);
528+
initializer.visitChildren(dependencyVisitor);
529+
for (final dependencyName in dependencyVisitor.dependencies) {
530+
if (adjList[dependencyName]!.add(dependerName)) {
531+
inDegrees[dependerName] = (inDegrees[dependerName] ?? 0) + 1;
532+
}
533+
}
534+
}
535+
}
536+
537+
// Perform topological sort
538+
final sortedOrder = <_ConstDeclarationInfo>[];
539+
final queue = <String>[];
540+
fieldNames.where((name) => inDegrees[name] == 0).forEach(queue.add);
541+
542+
while (queue.isNotEmpty) {
543+
final currentName = queue.removeAt(0);
544+
sortedOrder.add(fieldMap[currentName]!);
545+
for (final neighborName in adjList[currentName]!) {
546+
inDegrees[neighborName] = inDegrees[neighborName]! - 1;
547+
if (inDegrees[neighborName] == 0) {
548+
queue.add(neighborName);
549+
}
550+
}
551+
}
552+
553+
if (sortedOrder.length < constDecls.length) {
554+
final containerName = container is NamedCompilationUnitMember
555+
? '"${container.name.lexeme}"'
556+
: 'the library';
557+
throw CompileError(
558+
'Circular dependency detected in const declarations in $containerName.');
559+
}
560+
561+
// Compile sorted consts
562+
for (final declInfo in sortedOrder) {
563+
final parentNode = declInfo.variable.parent!.parent!;
564+
if (parentNode is TopLevelVariableDeclaration) {
565+
compileDeclaration(declInfo.variable, _ctx);
566+
alreadyCompiled.add(declInfo.variable);
567+
} else if (parentNode is FieldDeclaration &&
568+
declInfo.parent is ClassDeclaration) {
569+
_ctx.currentClass = declInfo.parent as ClassDeclaration;
570+
compileFieldDeclaration(
571+
-1, parentNode, _ctx, declInfo.parent as ClassDeclaration);
572+
_ctx.currentClass = null;
573+
alreadyCompiled.add(parentNode);
574+
}
575+
_ctx.resetStack();
576+
}
577+
});
578+
579+
// Compile non-statics
488580
_topLevelDeclarationsMap.forEach((key, value) {
489581
_ctx.topLevelDeclarationPositions[key] = {};
490582
_ctx.instanceDeclarationPositions[key] = {};
@@ -498,11 +590,18 @@ class Compiler implements BridgeDeclarationRegistry, EvalPluginRegistry {
498590
return;
499591
}
500592
final declaration = tlDeclaration.declaration!;
593+
594+
// We've already compiled all variable declarations that are top-level or static fields.
595+
// This pass is for compiling the bodies of classes and functions.
501596
if (declaration is ConstructorDeclaration ||
502597
declaration is MethodDeclaration ||
503598
declaration is VariableDeclaration) {
599+
// This skips top-level vars that were already compiled.
504600
return;
505601
}
602+
603+
if (alreadyCompiled.contains(declaration)) return;
604+
506605
_ctx.library = key;
507606
compileDeclaration(declaration, _ctx);
508607
_ctx.resetStack();
@@ -1216,3 +1315,41 @@ class _Import {
12161315
base.resolveUri(uri), import.prefix?.name, import.combinators);
12171316
}
12181317
}
1318+
1319+
// Helper class to hold const declarations for sorting
1320+
class _ConstDeclarationInfo {
1321+
_ConstDeclarationInfo(this.variable, this.parent, this.libraryIndex);
1322+
1323+
/// The VariableDeclaration itself (e.g., `b = 1.0`).
1324+
final VariableDeclaration variable;
1325+
1326+
/// The parent node (either a TopLevelVariableDeclaration or a ClassDeclaration).
1327+
final AstNode parent;
1328+
1329+
/// The library index where this declaration is defined.
1330+
final int libraryIndex;
1331+
1332+
String get name => variable.name.lexeme;
1333+
}
1334+
1335+
// Add this class at the end of compiler.dart
1336+
1337+
/// A simple visitor to find identifiers within an expression that match a given set of names.
1338+
class _DependencyVisitor extends RecursiveAstVisitor<void> {
1339+
_DependencyVisitor(this.availableNames);
1340+
1341+
/// The set of all possible dependency names we are looking for (e.g., all static const fields in the class).
1342+
final Set<String> availableNames;
1343+
1344+
/// The set of dependencies found in the visited expression.
1345+
final Set<String> dependencies = {};
1346+
1347+
@override
1348+
void visitSimpleIdentifier(SimpleIdentifier node) {
1349+
// If this identifier is one of the fields we are analyzing, it's a dependency.
1350+
if (availableNames.contains(node.name)) {
1351+
dependencies.add(node.name);
1352+
}
1353+
super.visitSimpleIdentifier(node);
1354+
}
1355+
}

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)