Skip to content

Module system#321

Draft
Y-Nak wants to merge 80 commits intoargotorg:mainfrom
Y-Nak:module-system
Draft

Module system#321
Y-Nak wants to merge 80 commits intoargotorg:mainfrom
Y-Nak:module-system

Conversation

@Y-Nak
Copy link
Member

@Y-Nak Y-Nak commented Feb 24, 2026

This PR is a PoC for the new module/namespace system and focuses on language behavior and import/export semantics.

The current implementation keeps a transitional flattening bridge so existing passes continue to work. If this direction is accepted, I'll start refactoring to remove that bridge by introducing module-aware lookup in name resolution/type checking and then cleaning up related paths.
TODOs:

  • External library support
  • Solidify the concept for the project root

Module and Namespace System

1. Entry File and Import Roots

  • The entry file is the path passed to -f / --file.
  • Import search roots are built in this order:
    1. takeDirectory(entryFile)
    2. directories from --include (colon-separated, default: std)
  • Imports are resolved by searching roots in order and picking the first existing file.

2. Module Identity and File Mapping

  • Module names are file-system driven.
  • import foo; resolves to foo.solc.
  • import foo.bar; resolves to foo/bar.solc.
  • Loaded module identity is the canonicalized file path.
  • A canonical file is loaded once, even if reached through multiple imports.

3. Import Syntax and Semantics

Supported forms:

import M;
import M as A;
import M.{X, Y};
import M.{*};

Behavior:

  • import M; and import M as A; add qualifier-based access only.
  • import M.{X, Y}; imports selected exported names into unqualified scope.
  • import M.{*}; imports all exported names into unqualified scope.
  • There is no open-import semantics.

Validation rules:

  • Duplicate qualifier names are rejected (import A as M; import B as M;).
  • Duplicate names inside one selective import are rejected.
  • Unknown selected names are rejected.
  • Ambiguous selected imports across modules are rejected.
  • Import cycles are rejected, with the cycle chain in the error.

Data constructor selection detail:

  • Selective import of a constructor (for example {True}) brings its parent data type declaration too, but only with selected constructors.

4. Export Syntax and Visibility

Supported forms:

export {X, Y};
export {*};

Current enforcement:

  • Imported modules must declare exactly one export declaration.
  • Entry module does not need an export declaration.
  • Multiple export declarations are rejected.
  • Duplicate names in an export list are rejected.
  • Unknown names in an export list are rejected.
  • export {*}; exports all importable top-level declarations (except pragma/export declarations).

5. Namespaces and Name Resolution

Duplicate checking is enforced separately for:

  • type namespace (contracts, data types, type synonyms)
  • class namespace
  • term namespace (functions, constructors, values)

Unqualified lookup order:

  1. Local lexical scope
  2. Current module top-level declarations
  3. Names introduced by import M.{...} / import M.{*}
  4. Otherwise unresolved

Module qualification:

  • Imported qualifiers are added to the resolver environment.
  • For nested module paths (for example import foo.bar;), prefix qualifiers are also registered for qualified access paths.

6. Constructor Model

Current constructor model is type-qualified constructors, with dot shorthand support.

  • Canonical constructor names are type-qualified (Bool.True, Option.Some).
  • Module-qualified constructor access is supported (mod.Bool.True, alias.Bool.True).
  • Unqualified constructors are generally rejected when only qualified constructors exist.

Dot shorthand:

.Some(1)
.None
  • Expression shorthand requires expected constructor type context.
  • Pattern shorthand requires expected scrutinee type context.
  • Missing expected type, no match, or ambiguous match is an error.

7. Pragma Scope

  • Pragmas are module-local.
  • Pragmas do not propagate to importing modules.
  • Pragmas are not transitive through imports.

Copy link
Collaborator

@mbenke mbenke left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alas, this PR breaks the contract tests (ERC20 et consortes):

$ ./run_contests.sh
Processing: test/examples/dispatch/basic.json
Compiling to Hull...
Configuration is affected by the following files:
- cabal.project.local
Emitting hull for contract C
Writing to output1.hull
Generating Yul...
Configuration is affected by the following files:
- cabal.project.local
yule: /home/ben/work/solcore/build/output1.hull:345:34:
    |
345 |         return dispatch_$impl$std.$impl$abi_decode$calldataLbytesJ_pairLuint256_pairLuint256_uint256JJ_CalldataWordReader_pairLuint256_pairLuint256_uint256JJ(decodable, pty, prdr)
    |                                  ^
unexpected '.'
expecting "assembly", "false", "fst", "function", "if", "in", "inl", "inr", "let", "match", "return", "revert", "snd", "true", '$', '(', '_', '{', '}', alphanumeric character, integer, or letter

CallStack (from HasCallStack):
  error, called at src/Common/LightYear.hs:26:15 in sol-core-0.0.0.0-inplace:Common.LightYear
  runMyParser', called at src/Common/LightYear.hs:21:22 in sol-core-0.0.0.0-inplace:Common.LightYear
  runMyParser, called at src/Language/Hull/Parser.hs:30:24 in sol-core-0.0.0.0-inplace:Language.Hull.Parser
Error: yule generation failed
Processing: test/examples/dispatch/neg.json
Compiling to Hull...
Configuration is affected by the following files:
- cabal.project.local
Module validation failed for /home/ben/work/solcore/test/examples/dispatch/neg.solc:
Undefined type constructor:
uint256

 - in:function negPair () -> uint256 {
   return uint256(fromB(pairfst(Neg.neg(Pair(B.F, B.T))))) ;
}
 - in:function negPair () -> uint256 {
   return uint256(fromB(pairfst(Neg.neg(Pair(B.F, B.T))))) ;
}
 - in:contract NegPair {
   constructor () {
   }
   function negPair () -> uint256 {
      return uint256(fromB(pairfst(Neg.neg(Pair(B.F, B.T))))) ;
   }
}
 - in:contract NegPair {
   constructor () {
   }
   function negPair () -> uint256 {
      return uint256(fromB(pairfst(Neg.neg(Pair(B.F, B.T))))) ;
   }
}
Error: sol-core compilation failed
Processing: test/examples/dispatch/miniERC20.json
Compiling to Hull...
Configuration is affected by the following files:
- cabal.project.local
Emitting hull for contract MiniERC20
Writing to output1.hull
Generating Yul...
Configuration is affected by the following files:
- cabal.project.local
yule: /home/ben/work/solcore/build/output1.hull:1294:34:
     |
1294 |         return dispatch_$impl$std.$impl$abi_decode$calldataLbytesJ_address_CalldataWordReader_address(decodable, pty, prdr)
     |                                  ^
unexpected '.'
expecting "assembly", "false", "fst", "function", "if", "in", "inl", "inr", "let", "match", "return", "revert", "snd", "true", '$', '(', '_', '{', '}', alphanumeric character, integer, or letter

CallStack (from HasCallStack):
  error, called at src/Common/LightYear.hs:26:15 in sol-core-0.0.0.0-inplace:Common.LightYear
  runMyParser', called at src/Common/LightYear.hs:21:22 in sol-core-0.0.0.0-inplace:Common.LightYear
  runMyParser, called at src/Language/Hull/Parser.hs:30:24 in sol-core-0.0.0.0-inplace:Language.Hull.Parser
Error: yule generation failed
Processing: test/examples/dispatch/Revert.json
Compiling to Hull...
Configuration is affected by the following files:
- cabal.project.local
Emitting hull for contract Foo
Writing to output1.hull
Generating Yul...
Configuration is affected by the following files:
- cabal.project.local
yule: /home/ben/work/solcore/build/output1.hull:200:34:
    |
200 |         return dispatch_$impl$std.$impl$abi_encode$uint256(val)
    |                                  ^
unexpected '.'
expecting "assembly", "false", "fst", "function", "if", "in", "inl", "inr", "let", "match", "return", "revert", "snd", "true", '$', '(', '_', '{', '}', alphanumeric character, integer, or letter

CallStack (from HasCallStack):
  error, called at src/Common/LightYear.hs:26:15 in sol-core-0.0.0.0-inplace:Common.LightYear
  runMyParser', called at src/Common/LightYear.hs:21:22 in sol-core-0.0.0.0-inplace:Common.LightYear
  runMyParser, called at src/Language/Hull/Parser.hs:30:24 in sol-core-0.0.0.0-inplace:Language.Hull.Parser
Error: yule generation failed

@Y-Nak Y-Nak marked this pull request as draft March 22, 2026 09:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants