Skip to content

Module Artifact And Typechecker Integration

Order

Recommended Issue order: 1. Module-system-local order: 1.

Goal

Introduce the shared module artifact model and make the type checker consume imports through that model. This is the root dependency for later native modules, bytecode modules, stdlib modularization, and cross-module trait impl visibility.

Dependencies

Prerequisites:

  • Current parser, typechecker, STUPID, and ORGASM module behavior.

Unblocks:

Scope

In scope:

  • ModuleId with canonical dotted names.
  • ModuleArtifact as the shared contract between parser, typechecker, STUPID, ORGASM, and later loaders.
  • ModuleResolver and load options for source modules.
  • NG_MODULE_PATH parsing and resolution priority.
  • Import/export metadata for values, functions, types, type aliases, traits, const predicates, and impl evidence.
  • Typechecker import integration through ModuleRegistry.
  • Duplicate visible impl diagnostics.

Out of scope:

  • Native descriptors.
  • .ngo binary format.
  • Stdlib physical reorganization.
  • Runtime bytecode import loading.

Core Model

cpp
enum class ModuleFormat {
  SourceNg,
  BytecodeNgo,
  Native,
};

struct ModuleId {
  Str canonicalName;      // e.g. "std.prelude"
  Vec<Str> pathSegments;  // e.g. ["std", "prelude"]
};

struct ModuleArtifact {
  ModuleId id;
  ModuleFormat format;
  Str originPath;
  Str version;
  ASTRef<CompileUnit> ast;
  TypeIndex typeIndex;
  BytecodeModule bytecode;
  RuntimeRef<StorageCell> runtimeModule;
  ModuleExportIndex exports;
  ModuleImportIndex imports;
  ModuleTraitIndex traits;
  ModuleImplIndex impls;
};

The artifact is the contract between stages:

  • Parser produces AST for .ng.
  • Type checker consumes artifact exports and publishes type/trait/impl metadata.
  • ORGASM compiler consumes artifact metadata and may publish bytecode later.
  • STUPID consumes runtime module metadata.

Module Identity

Module names are canonical dotted names:

ng
module std.prelude exports *;
import std.prelude (*);

Rules:

  • The canonical module ID is the dotted import path.
  • File path is only a resolution mechanism, not identity.
  • module foo.bar; inside a file must match import path foo.bar when imported by name.
  • A source file without a module declaration gets the canonical ID from its import path or CLI entrypoint.
  • Module identity must stay stable across source, bytecode, and native modules.

Search Path Resolution

Module resolution uses ordered roots:

  1. Explicit compiler/interpreter module paths.
  2. NG_MODULE_PATH, split by platform path separator.
  3. Current entry file directory.
  4. Project lib/.
  5. Installed standard library roots.
  6. Registered native module table, after Native Module Artifacts.

For import a.b.c, each filesystem root is probed with:

  • a/b/c.ng
  • a/b/c/module.ng

Bytecode probes are added later by Bytecode Module Loading.

Import Syntax

Keep the existing syntax:

ng
import std.prelude (*);
import std.string (join, split);
import vendor.math as math;
import vendor.math;

Normalize grammar:

ebnf
ImportDecl := ExportPrefix? "import" ModulePath ImportAlias? ImportList? ";"
ExportPrefix := "export"
ImportAlias := "as" Ident
ImportList := "(" ("*" | ImportItem ("," ImportItem)*) ")"
ImportItem := Ident

Compatibility rule:

  • Existing import a.b alias (...); remains accepted initially.
  • New examples and docs should use as.
  • export import m (*) explicitly re-exports imported symbols. It is the standard facade pattern for modules such as std.prelude.
  • import m; is a module-only import. The module value is available under the tail name (m) or explicit alias and members are accessed as m.symbol(...).
  • Symbol-level import aliases are deferred to Symbol Import Aliases.

Import Provenance And Conflicts

Every imported short name carries the canonical source module ID it came from. If a module re-exports an imported symbol, the original source module ID is preserved instead of being rewritten to the facade module.

Rules:

  • Re-importing the same short name from the same canonical source module is idempotent.
  • Importing the same short name from different canonical source modules is an error.
  • If both modules are needed, keep at least one import module-qualified: import first.module as first; import second.module as second;.
  • use impl Trait for Type resolves module qualifiers through the same canonical module alias table, so use impl first.Show for Foo remains unambiguous even when another module exports a different Show impl.
  • Short-name aliasing for individual imported symbols is deferred to Symbol Import Aliases.

Export Model

A module exports:

  • Values.
  • Functions.
  • Types and type aliases.
  • Traits.
  • Trait impl evidence.
  • Const predicates.

Export forms:

ng
module m exports *;
export fun f() -> unit { ... }
export type T { ... }
export trait Show { ... }
export impl Show for T { ... }
export import std.string (*);

Rules:

  • exports * exports local public definitions only.
  • Re-export requires explicit export of imported symbols.
  • Imported symbols are not re-exported by exports *.
  • export import is the explicit re-export form for imported symbols.
  • Impl evidence is not callable by name, but it participates in trait satisfaction and coherence.
  • export impl is the preferred explicit form for impl evidence.

Trait Impl Visibility

Visible impls come from:

  • Local module impls.
  • Imported modules' exported impl evidence.

Native module impl evidence is added later.

Rules:

  • Duplicate visible exact impl (Trait, Type) is an error.
  • Overlapping visible generic/concrete impls are an error unless selected by a later explicit selection feature.
  • Diagnostics must include trait name, target type, and candidate module IDs.

Acceptance Criteria

  • NG_MODULE_PATH affects source module lookup.
  • import std.prelude (*) resolves through ModuleRegistry.
  • Source modules expose exported functions, types, traits, and impl evidence to importers.
  • Imported impl evidence participates in trait satisfaction.
  • Duplicate visible impls from different modules fail deterministically.
  • STUPID and ORGASM agree on import/export visibility for source modules.

Implementation Status

Implemented in this phase:

  • ModuleId, ModuleFormat, ModuleArtifact, export/import/trait/impl indexes, and canonical module-id helpers.
  • Source module loading through ordered roots, including explicit module paths, NG_MODULE_PATH, current directory, and stdlib root.
  • Source probes for both a/b/c.ng and a/b/c/module.ng.
  • Explicit dotted module declarations, including module std.prelude exports *;.
  • import vendor.math as math; syntax while preserving the older alias form.
  • export import ... parsing and STUPID runtime re-export support.
  • Runtime import provenance for de-duplicating same-source imports while rejecting conflicting same-name imports from different modules.
  • ORGASM rejects conflicting same-name short imports and supports module-qualified calls for source/bytecode module imports.
  • Typechecker publication of exported type metadata, traits, and exported impl evidence into ModuleRegistry.
  • Typechecker import consumption through published ModuleArtifact metadata, including duplicate visible impl diagnostics with candidate module IDs.
  • STUPID and ORGASM reuse of registry module entries only when the stage-specific payload exists, avoiding empty runtime/bytecode artifacts.
  • ORGASM import tables now store canonical module IDs, and exports * publishes module functions for bytecode imports.

Still intentionally deferred to later module-system issues:

  • Native module descriptors and native artifact publication.
  • .ngo bytecode serialization/deserialization and bytecode-first probing.
  • Physical stdlib modularization beyond the canonical std.prelude declaration.
  • Symbol-level import aliases, tracked separately in Symbol Import Aliases.
  • Full ORGASM bytecode metadata for re-exported imported native/source symbols.

Made with ❤️ by the NG community.