Modules and Imports

A Sec module is the shared source namespace formed by one source directory. All applicable Sec source files directly in that directory contribute declarations to the same module.

domain/orders/
    order.sec
    status.sec
    validation.sec

Each ordinary source file in that directory declares:

module orders

External modules are accessed through explicit logical imports:

import "net/http"

fn Handle() void {
    let cookie := http.Cookie(...)
}
Core rule: Module declarations are module-wide facts. Import bindings are source-file-local.

One Directory Forms One Module

One source directory corresponds to one source module.

services/database/
    connection.sec
    query.sec
    transaction.sec

All participating files declare:

module database

The files do not become separate modules merely because they are separate files.

Conversely, subdirectories form their own source modules rather than being folded automatically into the parent module.

Rule: Source-file boundaries organize code. Directory boundaries define ordinary source-module membership.

Module Declaration

Every ordinary Sec source file declares the module to which it belongs:

module orders

The declaration appears as one module identifier.

It is not a filesystem path:

module domain.orders          // invalid
module internal.storage       // invalid
module platform.linux.amd64   // invalid

Directory hierarchy belongs to import resolution. It is not encoded as a dotted source-level module name.

Module Name

For an ordinary directory module, the expected module name is the final source-directory component.

Source directory Module declaration
domain/orders/ module orders
services/database/ module database
internal/storage/ module storage
services/internal/transport/ module transport

The project root follows the root-module rules described in Projects and sec.module.

Module Name and Module Identity Are Different

The short source module name is not globally unique.

These may both be valid:

sales/order/
    module order

customer/order/
    module order

The modules remain distinct because their canonical import locations and import roots differ.

Source code normally sees the short module qualifier. The compiler tracks a stronger canonical module identity so that declarations from unrelated modules with the same short name never collapse into one semantic module.

Target selection may create different compilation instances of the same logical module without changing the logical module's identity.

Multi-File Modules

A module may be split across as many directly participating source files as is useful.

order.sec:

module orders

type CustomerOrder struct {
    Id: uint64
}

create.sec:

module orders

fn CreateCustomerOrder() CustomerOrder {
    return CustomerOrder {
        Id: 0
    }
}

No import is needed between these files.

They are two source files contributing to one module namespace.

All Files Share the Module Declaration Namespace

Module-level declarations from sibling files are visible according to their ordinary visibility rules.

Duplicate-declaration checking therefore considers the complete module, not one file at a time.

Declaration forms that explicitly allow merging or overloading still follow their own rules:

  • function overload groups;
  • impl and impl extends;
  • nested declarations;
  • other declaration-specific merge rules.

Splitting code into files never grants permission to declare two otherwise-conflicting module members with the same identity.

Source-File Order Has No Semantic Meaning

Files in one module do not have a semantic compilation order.

A declaration in one file may refer to another declaration in the same module regardless of:

  • filename sorting;
  • filesystem enumeration order;
  • editor-open order;
  • parallel compiler worker order.

The compiler assembles the complete relevant declaration surface before ordinary body name resolution depends on source-file order.

Visibility Across Module Files

Sec's name prefixes retain their ordinary visibility meaning:

Spelling Visibility
Name Public
_name Module-internal
__name Source-file-private

A module-internal declaration may be used from sibling source files in the same module.

A source-file-private declaration remains private to its declaring file even though sibling files belong to the same module.

Module-path access restrictions and declaration visibility are separate checks.

Imports

Imports bind an external module to a qualifier in the current source file.

module server

import "net/http"

fn Serve() void {
    http.Listen(...)
}

Importing a module does not merge its declarations into the current module.

Imported declarations remain owned by their original module.

Import Paths Are Logical Paths

Sec imports use canonical logical import paths:

import "domain/orders"
import "net/http"

Canonical import paths:

  • use / as the separator on every host platform;
  • are case-sensitive;
  • are not host filesystem paths;
  • do not contain . or .. traversal components;
  • do not use host-specific path separators;
  • are resolved through Sec's import roots.

These are invalid:

import "../orders"
import "./orders"
import "/srv/project/orders"

The same source import has the same logical spelling on Linux, Windows, macOS and other supported hosts.

Standard Library Imports

Standard-library modules use their canonical logical path directly.

import "fmt"
import "io"
import "net/http"
import "net/dns"

There is no std/ prefix.

The compiler resolves canonical standard-library paths through the standard-library namespace before ordinary project/dependency fallback.

A project-local module must not silently replace a canonical standard-library module that owns the same logical path.

Platform Imports

platform is a compiler-reserved import root.

import "platform/linux/amd64"

Platform source selection is governed by the active compilation plan and target model.

If a reserved-root import fails, the compiler does not retry the same spelling as a project-local import.

This prevents accidental shadowing of compiler-owned platform identities.

Project-Local Imports

Ordinary non-reserved imports resolve within the active project source root.

import "domain/orders"
import "services/database"

The Project chapter defines how the source root and build inputs are selected.

Module semantics remain based on logical import identity rather than arbitrary paths relative to the importing file.

The Default Qualifier Comes from the Imported Module

Consider:

import "customer/order"

If the imported module declares:

module order

the default source qualifier is:

order.CreateCustomerOrder()

not:

customer.order.CreateCustomerOrder()    // invalid
customer/order.CreateCustomerOrder()    // invalid

The import path identifies the module. The module declaration provides its normal short source qualifier.

Import Bindings Are Source-File-Local

Imports do not become module-wide bindings.

create.sec:

module orders

import "database"

fn Create() void {
    database.Insert()
}

A sibling file in module orders does not automatically gain the database qualifier.

If that sibling file also needs the module, it declares its own import:

module orders

import "database"

fn Remove() void {
    database.Delete()
}
Rule: Declarations are module-wide. Import qualifiers are local to the file that imports them.

Import Aliases

An import can provide a local qualifier:

import customerOrder "customer/order"

Use the alias in that source file:

customerOrder.CreateCustomerOrder()

The alias changes only the source-file-local binding.

It does not change:

  • the imported module's identity;
  • the imported module's own module name;
  • declaration ownership;
  • cross-module semantic identity;
  • linking identity.

Qualifier Collisions

Two imported modules may have the same short module name. The current source file still needs unique local qualifiers.

import (
    "sales/order"
    customerOrder "customer/order"
)

Usage:

order.Ascending()
customerOrder.CreateCustomerOrder()

Without the alias, both imports would attempt to bind the local qualifier order.

Aliases and imported module bindings participate in the ordinary unified source-scope naming rules.

Imported Declarations Use Qualified Access

After:

import "storage"

use:

storage.Open()

Imported module declarations are not injected into the current source scope as unqualified names.

This keeps module ownership visible in source and avoids ambiguity as projects grow.

No Implicit Name Injection

This is not valid solely because storage was imported:

Open()

Use the module qualifier:

storage.Open()

A local declaration named Open remains a separate local/module name.

Imports Are Not Re-Exports

Suppose module service imports database:

module service

import "database"

A third module that imports service does not automatically obtain a database binding.

Ordinary imports are dependencies of the importing file/module, not public forwarding declarations.

Sec 0.1 does not define explicit re-export syntax.

No Selective or Wildcard Imports

Sec 0.1 does not define forms such as:

import { Open, Close } from "storage"    // invalid
import * from "storage"                  // invalid
from "storage" import Open               // invalid

Import the module and use its qualifier:

import "storage"

storage.Open()
storage.Close()

internal Modules

The path component internal is an import-access marker.

services/internal/transport/

The module inside still declares:

module transport

internal remains part of the canonical import path and module identity, but it is not part of the module declaration or default source qualifier.

internal Access Scope

A module below an internal directory may be imported only by modules below the directory immediately containing that internal directory.

For:

services/
    internal/
        transport/

modules within the services subtree may use that internal module. Modules outside that enclosing subtree may not.

A root-level internal/ directory is private to the complete project.

This access check occurs before declaration visibility. A public declaration inside an inaccessible internal module does not make the module importable.

Modules Cannot Import Themselves

A module must not create an import edge to its own canonical module identity.

Same-module sibling files need no import at all.

If code in orders/create.sec needs a declaration from orders/status.sec, it simply uses that declaration according to normal same-module visibility.

Duplicate Imports

One source file must not import the same canonical module identity more than once.

Different aliases do not make duplicate imports distinct modules:

import first "customer/order"
import second "customer/order"    // invalid duplicate module import

Different source files may independently import the same dependency because each file has its own import-binding scope.

Import Cycles Are Invalid

The Sec 0.1 module import graph is acyclic.

This is invalid:

orders
    imports billing

billing
    imports inventory

inventory
    imports orders

The cycle is rejected regardless of:

  • import aliases;
  • source-file order;
  • whether declarations happen to resolve under a partial compilation order.

Cycle detection uses canonical module identities, not local source aliases.

The Module Dependency Graph

Every source-file import contributes an edge to the containing module's dependency set.

Conceptually:

source-file imports
        ↓
module dependency set
        ↓
resolved ModuleGraph

Once self-imports, duplicate imports and cycles are rejected, the module dependency graph is a directed acyclic graph.

The compiler can then process module dependencies deterministically without making filesystem enumeration order semantic.

The Import Graph Is Not the Initialization Order

Imports describe semantic module dependencies.

They do not by themselves define:

  • static initialization order;
  • runtime startup order;
  • startup failure behavior;
  • deinitialization order.

Program initialization consumes module dependency facts but has its own lifecycle rules.

Rule: “A imports B” does not by itself mean “B executes initialization immediately before A”.

Public Module Surface

A module's external semantic surface consists of the public declarations and semantic information another module needs in order to use them.

This includes, where applicable:

  • public types;
  • public functions;
  • public properties;
  • public interfaces and conformance relationships;
  • public generic signatures and constraints;
  • other public declaration identities required for type checking.

Imported module bindings are not automatically part of the module's public surface.

The compiler may represent this surface internally in whatever form preserves the required semantics.

Public Signatures Cannot Expose Inaccessible Types

A public declaration used from another module must not require that importer to name or understand an inaccessible declaration.

For example, a public function cannot expose a module-internal type in its signature:

type _InternalState struct {
    ...
}

fn CurrentState() _InternalState {    // invalid public surface
    ...
}

The same principle applies, where relevant, to:

  • parameter types;
  • return types;
  • public fields;
  • public properties;
  • interface relationships;
  • generic constraints and arguments.

Test Files and Module Participation

Production module instances exclude *_test.sec files.

During sec test, a test compilation may combine:

  • the ordinary production files of the module;
  • same-directory *_test.sec files.

Same-directory test files participate in the same test-view module and therefore have ordinary same-module access.

They still do not gain access to declarations that are source-file-private in another file.

Production code must never depend on test-only declarations.

Project-root tests/ modules are test-only external modules and use ordinary imports and external visibility rules.

Entry Modules Are a Project Role

A project target may designate an entry module.

Entry status does not change:

  • the module namespace;
  • visibility rules;
  • import semantics;
  • module identity;
  • ordinary declaration ownership.

Entry selection belongs to the project/build model rather than the source module declaration itself.

Compilation Plans Select Module Instances

One logical module may be analyzed under more than one concrete compilation plan when target-dependent source selection differs.

A Linux AMD64 build and a bare-metal Cortex-M build therefore do not become one polymorphic module graph.

Each concrete compilation plan receives the module instances applicable to that plan.

The logical identity of a target-independent module does not change merely because the module is compiled for another target.

Sec 0.1 does not define conditional-import source syntax.

Separate and Incremental Compilation

Modules are designed so importers can resolve public declarations without requiring every imported implementation body to be reanalyzed from scratch.

Conceptually, the compiler can derive a deterministic semantic module surface:

ModuleIdentity
PublicDeclarations
SemanticSignatures
RequiredTypeInformation
SurfaceFingerprint

An implementation-only edit that preserves the public semantic surface need not invalidate ordinary importer name/type resolution.

A public-signature change must invalidate dependent semantic state where necessary.

These are compiler/tooling properties; they do not add source syntax.

Editor and LSP Behavior

The LSP uses the same module resolver and active compilation-plan model as command-line compilation.

For an open source file, tooling can therefore reason about:

  • its module identity;
  • its declared module name;
  • same-module sibling declarations;
  • file-local import aliases;
  • module dependency identities;
  • internal accessibility;
  • the public surface of imported modules.

Editing one file updates that file's imports immediately and invalidates wider module state only as required by the changed dependency or public surface.

A qualifier rename normally changes the local import alias rather than renaming the imported module itself.

Common Invalid Forms

Dotted module declaration

module domain.orders

Relative filesystem import

import "../orders"

Absolute filesystem import

import "/srv/project/orders"

Implicit unqualified imported member

import "storage"

Open()    // invalid solely because storage was imported

Duplicate canonical import

import first "customer/order"
import second "customer/order"

Import cycle

A imports B
B imports A

Wildcard import

import * from "storage"

Diagnostics

Important module diagnostics include:

  • missing module declaration;
  • module declaration inconsistent with the source directory;
  • sibling files declaring different module names;
  • unresolved import;
  • malformed or non-canonical import path;
  • illegal internal import;
  • duplicate import of the same canonical module identity;
  • self-import;
  • qualifier or alias collision;
  • import cycle;
  • duplicate module-scope declaration;
  • public surface exposing an inaccessible declaration or type.

Directory/module mismatch

module declaration does not match source directory

file:
    domain/orders/status.sec

declared:
    module status

expected:
    module orders

Qualifier collision

both imports bind the qualifier `order`

    import "sales/order"
    import "customer/order"

help: give one import an explicit local alias

Import cycle

module import cycle detected

orders -> billing -> inventory -> orders

Cycle diagnostics should point to the import sites forming a short deterministic representative cycle.

Best Practices

  • Use one cohesive source directory per module.
  • Keep the module declaration equal to the directory's module name.
  • Split large modules into files freely; sibling files already share the module namespace.
  • Declare imports only in files that actually use the external module.
  • Use direct standard-library paths such as "fmt" and "net/http".
  • Use import aliases when two dependencies have the same short module name.
  • Keep imported names qualified rather than trying to simulate wildcard imports.
  • Use internal directories for subtree-level implementation modules that should not be imported externally.
  • Avoid cyclic module design; extract shared contracts into a lower dependency when necessary.
  • Do not rely on filename or filesystem enumeration order for declaration visibility.
  • Keep public signatures free of inaccessible implementation-only types.
  • Remember that import dependencies and program initialization order are different concerns.

Summary

  • One ordinary source directory forms one Sec module.
  • All applicable .sec files directly in that directory contribute to one shared module namespace.
  • Every ordinary source file declares its module with module Name.
  • The normal module name is the final source-directory component.
  • A module declaration contains one identifier and is not a dotted filesystem path.
  • The compiler tracks canonical module identity separately from the short module name.
  • Different modules may legally share the same short module name.
  • No import is required between files belonging to the same module.
  • Duplicate-declaration checks operate over the complete module namespace.
  • Source-file and filesystem enumeration order have no semantic meaning.
  • Public names are externally visible subject to module access rules.
  • _name is module-internal.
  • __name is source-file-private.
  • Module access and declaration visibility are separate checks.
  • Imports use quoted canonical logical paths such as import "net/http".
  • Import paths use / on every host platform.
  • Import paths are case-sensitive and do not use ., .., absolute host paths or host-specific separators.
  • Standard-library modules use direct logical paths without a std/ prefix.
  • Canonical standard-library paths cannot be silently shadowed by project-local modules.
  • platform is a compiler-reserved import root.
  • Failed reserved-root imports do not silently fall back to project-local resolution.
  • Ordinary non-reserved imports resolve within the active project source root.
  • The default imported qualifier comes from the imported module's module declaration.
  • The canonical import path does not become a dotted source qualifier.
  • Import bindings are source-file-local.
  • Sibling files do not inherit one another's imports.
  • Import aliases create local qualifiers only and do not change module identity.
  • One source file must have unique import qualifiers.
  • The same canonical module identity cannot be imported twice by one source file.
  • Imports do not inject declarations as unqualified names.
  • Ordinary imports are not re-exports.
  • Sec 0.1 has no explicit re-export syntax.
  • Sec 0.1 has no selective-import or wildcard-import syntax.
  • The path component internal is an access-control marker, not part of the source module name.
  • An internal module is importable only from the subtree allowed by its enclosing internal boundary.
  • A root-level internal directory is private to the project.
  • A public declaration cannot override an inaccessible internal-module boundary.
  • A module cannot import itself.
  • The Sec 0.1 module import graph must be acyclic.
  • Cycle detection uses canonical module identities rather than aliases.
  • Module dependency edges are the union of imports from the module's participating source files.
  • The module import graph is not automatically the program initialization or deinitialization graph.
  • A public module surface contains the public semantic information required by importers.
  • Public signatures cannot expose inaccessible declarations that external users need to name or understand.
  • Production module instances exclude *_test.sec.
  • A test compilation may combine production files with same-directory *_test.sec files.
  • Same-directory test files gain ordinary same-module visibility but not another file's source-private names.
  • Production declarations must not depend on test-only declarations.
  • Entry-module status is a project/build role and does not change module semantics.
  • Each concrete compilation plan receives the module instances selected for that plan.
  • Target-specific compilation does not automatically create a different logical module identity.
  • Sec 0.1 has no conditional-import source syntax.
  • Module surfaces support deterministic separate and incremental compilation.
  • Implementation-only changes need not invalidate importers when the public semantic surface remains unchanged.
  • The LSP uses the same module-resolution and compilation-plan model as command-line compilation.