diff --git a/src/context.ts b/src/context.ts index b6cba67..da722b2 100644 --- a/src/context.ts +++ b/src/context.ts @@ -22,6 +22,8 @@ export type CrateLoader = ( export class GlobalContext { public error: ErrorHandler = new ErrorHandler(); public finalizedCrates: Crate[] = []; + // For cycle detection. + public cratesBeingLoaded: Set = new Set(); public crateId: Ids = new Ids(); constructor(public opts: Options, public crateLoader: CrateLoader) {} diff --git a/src/loader.ts b/src/loader.ts index 3ebdbb0..28f550b 100644 --- a/src/loader.ts +++ b/src/loader.ts @@ -90,9 +90,7 @@ export const loadCrate: CrateLoader = ( name: string, span: Span, ): DepCrate => { - // We really, really want a good algorithm for finding crates. - // But right now we just look for files in the CWD. - + // If we've loaded the crate already, great. const existing = gcx.finalizedCrates.find( (crate) => crate.packageName === name, ); @@ -102,6 +100,24 @@ export const loadCrate: CrateLoader = ( const crateId = gcx.crateId.next(); + // If we have not loaded the crate yet, we may actually already be loading it. + // A cycle!! + if (gcx.cratesBeingLoaded.has(name)) { + return dummyErrorCrate( + crateId, + name, + gcx.error.emit( + new CompilerError(`cycle detected loading extern module ${name}`, span), + ), + ); + } + + // Let's start loading the crate! + gcx.cratesBeingLoaded.add(name); + + // We really, really want a good algorithm for finding crates. + // But right now we just look for files in the CWD. + const file = loadModuleFile(".", name, span); if (!file.ok) { return dummyErrorCrate(crateId, name, gcx.error.emit(file.err)); @@ -122,5 +138,7 @@ export const loadCrate: CrateLoader = ( const typecked = typeck(gcx, resolved); gcx.finalizedCrates.push(typecked); + // Crate is loaded, no cycles. + gcx.cratesBeingLoaded.delete(name); return typecked; };