import { VariableDeclarations } from "@pseuco/lang"; import { ArrayType } from "./types/ArrayType"; import { ClassDeclarator } from "./environments/TypeEnvironment"; import { TypeCheckCoordinator } from "./TypeCheckCoordinator"; import { ClassType } from "./types/ClassType"; import { DeclarationType } from "./types/DeclarationType"; export class ClassReferenceCycleDetection { // `classRecords` is a simplified representation of class declarations, that contains only information relevant for this task private classRecords: ClassRefRecord[]; // `remainingClasses` is a list of class names from which did not yet prove that they are not part of a cyclic dependency private remainingClasses: string[]; constructor (readonly coordinator: TypeCheckCoordinator, classes: ClassDeclarator[]) { // `classRecords` and `remainingClasses` is constructed from an array of Monitor, respectively Struct nodes this.classRecords = classes.map(classDecl => ClassRefRecord.fromClassDeclarator(classDecl, this.coordinator)); this.remainingClasses = this.classRecords.map(r => r.className); } public findCycles(): string[] | null { // References to classes for which we proved already that they are not part of a cycle can be removed from the class reference records const newClassRecords = this.classRecords.map(record => record.removeClassesWithReferencesOnlyTo(this.remainingClasses)); // Classes that do not have any reference to another (of the remaining) classes are for sure not part of a cyclic dependency and can be removed from further checks const relevantClassRecords = newClassRecords.filter(r => r.referencedClassNames.length > 0); // If the array is empty, we know that there are no cycles if (relevantClassRecords.length == 0) { return null; } // If the number of relevant records did not decrease, there is a cycle if (relevantClassRecords.length == this.remainingClasses.length) { return this.constructCycle(relevantClassRecords); } // Otherwise, continue with the reduced set of classes this.remainingClasses = relevantClassRecords.map(r => r.className); return this.findCycles(); } private constructCycle(relevantClassRecords: ClassRefRecord[]): string[] { // Initialise the trace by choosing the any of the relevant classes const res = [relevantClassRecords[0]]; // We need at least relevantClassRecords.length+1 steps for (var i = 0; i <= relevantClassRecords.length; i++) { // Obtain the most recently added class record const lastRecord = res[res.length - 1]; // Follow any of the references in this class and find the corresponding class record const next = relevantClassRecords.find(r => r.className == lastRecord.referencedClassNames[0]); if (next == undefined) { // This should not happen throw new Error("Internal inconsistency error."); } // Check if we found a cycle already; then add it to `res` const cycleDiscovered = res.includes(next); res.push(next); // If we found a cycle, return it if (cycleDiscovered) { return res.map(r => r.className); } } // This line should not be reachable if there is really a cycle in the above classes! throw new Error("Internal inconsistency error.") } } class ClassRefRecord { constructor(readonly className: string, readonly referencedClassNames: string[] = new Array()) { } public removeClassesWithReferencesOnlyTo(references: string[]): ClassRefRecord { const filteredRefClasses = this.referencedClassNames.filter(refName => references.includes(refName)); return new ClassRefRecord(this.className, filteredRefClasses); } static fromClassDeclarator(classDecl: ClassDeclarator, coordinator: TypeCheckCoordinator): ClassRefRecord { const className = classDecl.name; const refs = new Array(); classDecl.declarations.forEach(decl => { if (decl instanceof VariableDeclarations) { var declType = (coordinator.typeForNode(decl.typeNode) as DeclarationType).declaredType; while (declType instanceof ArrayType) { declType = declType.elementsType; } if (declType instanceof ClassType) { const refClassName = declType.className; refs.push(refClassName); } } }, this); return new ClassRefRecord(className, refs); } }