Component
Forge
Describe the bug
With dynamic_test_linking = true, the preprocessor runs an in-process Solar analysis over the compiler input to find tests' bytecode dependencies. On several popular projects this analysis fails, even though the same projects compile without errors with Solar's CLI, forge build --use solar, and solc.
Each failure has the same root cause: the analysis loads the same file twice, under an absolute path and a relative path. Remappings passed to the analysis parser are absolute (forge-std/ → /abs/path/lib/forge-std/src/), while input source keys and relative imports (./Base.sol) use root-relative paths.
Solar's file resolver treats /abs/.../lib/forge-std/src/Base.sol and lib/forge-std/src/Base.sol as two different files. Trace from v4-core (RUST_LOG=solar_interface=trace):
resolve_file{path=forge-std/Base.sol}: remapped=/home/agent/dl/v4/lib/forge-std/src/Base.sol
resolve_file{path=forge-std/Base.sol}:try_file{path=/home/agent/dl/v4/lib/forge-std/src/Base.sol}: loaded from cache 1
resolve_file{path=./Base.sol}:try_file{path=lib/forge-std/src/Base.sol}: loaded from cache 2
In a trace of one OZ build, EIP712.sol is resolved 4 times via the absolute path and 10 times via the relative path. In one Seaport build, ConsiderationEnums.sol is resolved 263 times absolute and 8 times relative. The two copies then produce duplicate declarations, or a base-contract identity that doesn't match the inherited base. Every analysis error in these projects (forge build, dyn=true, Solar parser):
| project |
errors |
Uniswap/v4-core 46c6834 |
10 × identifier X already declared in lib/forge-std/src/Base.sol, for vm, stdstore, VM_ADDRESS, UINT256_MAX, SECP256K1_ORDER, MULTICALL3_ADDRESS, DEFAULT_TEST_CONTRACT, DEFAULT_SENDER, CREATE2_FACTORY, CONSOLE. The "previous declaration" points at the same span. |
ProjectOpenSea/seaport 7f966fe |
already declared for ItemType ×3, OrderType ×2, BasicOrderType ×2, console, Side, ContractNonceDetails (e.g. lib/seaport-sol/src/SeaportSol.sol:5) |
OpenZeppelin/openzeppelin-contracts 32b5b8c |
13 × expected base class or modifier, found abstract contract for EIP712(...) in constructors of contracts that inherit EIP712 indirectly (e.g. test/account/paymaster/PaymasterSigner.t.sol:12) |
solmate, solady, morpho-blue and forge-std have no analysis failures. Compiling the same projects with Solar's CLI, or forge build --use solar, reports none of these errors.
Impact
The failure makes forge give up on dynamic test linking for the whole compiler job. After that, every edit recompiles every test file: OpenZeppelin recompiles 31 files instead of 1, v4-core 60, and seaport 74. Details and numbers are in #17222, which covers the fallback behavior separately.
Repro
git clone --recursive https://github.com/Uniswap/v4-core && cd v4-core
FOUNDRY_DYNAMIC_TEST_LINKING=true RUST_LOG=foundry_common::preprocessor=warn forge build
# WARN ... dynamic test linking analysis failed; using native bytecode err=solar reported errors:
# error: identifier `VM_ADDRESS` already declared --> lib/forge-std/src/Base.sol:9:31
# note: previous declaration declared here --> lib/forge-std/src/Base.sol:9:31
echo "// edit" >> test/PoolManager.t.sol
FOUNDRY_DYNAMIC_TEST_LINKING=true forge build # Compiling 60 files
Versions: forge nightly e342985 (1.8.4-nightly), solar 0.2.0 251aea2, solc 0.8.26 / 0.8.24 / 0.8.35.
Suggestions
Use one path form in the analysis session: pass root-relative remappings to the parser to match the relative source keys, or make every input path absolute. Alternatively, have Solar's file resolver normalize paths against its base path, so both forms map to the same source file.
Related: #17222 (fallback cost), #17204.
Component
Forge
Describe the bug
With
dynamic_test_linking = true, the preprocessor runs an in-process Solar analysis over the compiler input to find tests' bytecode dependencies. On several popular projects this analysis fails, even though the same projects compile without errors with Solar's CLI,forge build --use solar, and solc.Each failure has the same root cause: the analysis loads the same file twice, under an absolute path and a relative path. Remappings passed to the analysis parser are absolute (
forge-std/→/abs/path/lib/forge-std/src/), while input source keys and relative imports (./Base.sol) use root-relative paths.Solar's file resolver treats
/abs/.../lib/forge-std/src/Base.solandlib/forge-std/src/Base.solas two different files. Trace from v4-core (RUST_LOG=solar_interface=trace):In a trace of one OZ build,
EIP712.solis resolved 4 times via the absolute path and 10 times via the relative path. In one Seaport build,ConsiderationEnums.solis resolved 263 times absolute and 8 times relative. The two copies then produce duplicate declarations, or a base-contract identity that doesn't match the inherited base. Every analysis error in these projects (forge build, dyn=true, Solar parser):46c6834identifier X already declaredinlib/forge-std/src/Base.sol, forvm,stdstore,VM_ADDRESS,UINT256_MAX,SECP256K1_ORDER,MULTICALL3_ADDRESS,DEFAULT_TEST_CONTRACT,DEFAULT_SENDER,CREATE2_FACTORY,CONSOLE. The "previous declaration" points at the same span.7f966fealready declaredforItemType×3,OrderType×2,BasicOrderType×2,console,Side,ContractNonceDetails(e.g.lib/seaport-sol/src/SeaportSol.sol:5)32b5b8cexpected base class or modifier, found abstract contractforEIP712(...)in constructors of contracts that inheritEIP712indirectly (e.g.test/account/paymaster/PaymasterSigner.t.sol:12)solmate, solady, morpho-blue and forge-std have no analysis failures. Compiling the same projects with Solar's CLI, or
forge build --use solar, reports none of these errors.Impact
The failure makes forge give up on dynamic test linking for the whole compiler job. After that, every edit recompiles every test file: OpenZeppelin recompiles 31 files instead of 1, v4-core 60, and seaport 74. Details and numbers are in #17222, which covers the fallback behavior separately.
Repro
Versions: forge nightly
e342985(1.8.4-nightly), solar 0.2.0251aea2, solc 0.8.26 / 0.8.24 / 0.8.35.Suggestions
Use one path form in the analysis session: pass root-relative remappings to the parser to match the relative source keys, or make every input path absolute. Alternatively, have Solar's file resolver normalize paths against its base path, so both forms map to the same source file.
Related: #17222 (fallback cost), #17204.