Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion doc/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@ The `crate` keyword is used to construct absolute paths where the path root is t
use crate::math::add;
```

The compiler driver (`simc`) automatically maps the `crate` keyword to the directory containing the entry-point file. For external libraries linked via `--dep`, the driver also maps `crate` to the library's root, ensuring that `use crate::...` statements inside the library resolve correctly within that library's scope.
The compiler driver (`simc`) maps the `crate` keyword to the project root. By default, this is the directory containing the entry-point file.
For projects whose entry point is in a subdirectory, pass `--project-root <PATH>` to set the root explicitly.
For external libraries linked via `--dep`, the driver also maps `crate` to the library's root, ensuring that `use crate::...` statements inside the library resolve correctly within that library's scope.

### Strict Local Imports

Expand Down
31 changes: 26 additions & 5 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,15 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
.action(ArgAction::Append)
.help("Link a dependency, optionally scoped to a specific module (e.g., --dep ./libs/merkle:math=./libs/math)"),
)
.arg(
Arg::new("project_root")
.long("project-root")
.value_name("PROJECT_ROOT")
.action(ArgAction::Set)
.help(
"Project root for resolving `crate::` imports (defaults to the entry file's directory)",
),
)
.arg(
Arg::new("wit_file")
.long("wit")
Expand Down Expand Up @@ -157,11 +166,23 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
.get_many::<String>("dependencies")
.unwrap_or_default();

let canon_root = main_path
.as_path()
.parent()
.and_then(|p| CanonPath::canonicalize(p).ok())
.ok_or("Failed to determine project root directory from entry file")?;
let canon_root = match matches.get_one::<String>("project_root") {
Some(project_root) => CanonPath::canonicalize(Path::new(project_root))?,
None => main_path
.as_path()
.parent()
.and_then(|p| CanonPath::canonicalize(p).ok())
.ok_or("Failed to determine project root directory from entry file")?,
};

if !main_path.starts_with(&canon_root) {
Comment thread
KyrylR marked this conversation as resolved.
return Err(format!(
"Entry file '{}' is outside project root '{}'",
main_path.as_path().display(),
canon_root.as_path().display()
)
.into());
}

let mut builder = DependencyMapBuilder::new();

Expand Down
137 changes: 137 additions & 0 deletions tests/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,143 @@ fn cli_dependency_can_use_crate_root() {
);
}

#[test]
fn cli_project_root_resolves_crate_from_nested_entry() {
let root = setup_project(
"nested_entry_project_root",
&[
(
"src/main.simf",
"simc \"*\";\nuse crate::utils::helper;\nfn main() { assert!(jet::eq_32(helper(), 42)); }\n",
),
(
"utils.simf",
"simc \"*\";\nuse support::values::answer;\npub fn helper() -> u32 { answer() }\n",
),
(
"src/utils.simf",
"simc \"*\";\npub fn wrong_helper() -> u32 { 0 }\n",
),
(
"deps/support/values.simf",
"simc \"*\";\npub fn answer() -> u32 { 42 }\n",
),
],
);

let output = Command::new(env!("CARGO_BIN_EXE_simc"))
.current_dir(&root)
.arg("src/main.simf")
.arg("--project-root")
.arg(".")
.arg("-Z")
.arg("imports")
.arg("--dep")
.arg("support=deps/support")
.output()
.expect("failed to run simc");

assert!(
output.status.success(),
"simc failed\nstatus: {:?}\nstdout:\n{}\nstderr:\n{}",
output.status.code(),
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}

#[test]
fn cli_nested_entry_defaults_project_root_to_entry_parent() {
let root = setup_project(
"nested_entry_default_root",
&[
(
"src/main.simf",
"simc \"*\";\nuse crate::utils::helper;\nfn main() { assert!(jet::eq_32(helper(), 7)); }\n",
),
(
"src/utils.simf",
"simc \"*\";\npub fn helper() -> u32 { 7 }\n",
),
(
"utils.simf",
"simc \"*\";\npub fn wrong_helper() -> u32 { 0 }\n",
),
],
);

let output = Command::new(env!("CARGO_BIN_EXE_simc"))
.arg(root.join("src/main.simf"))
.arg("-Z")
.arg("imports")
.output()
.expect("failed to run simc");

assert!(
output.status.success(),
"entry-parent default failed\nstderr:\n{}",
String::from_utf8_lossy(&output.stderr),
);
}

#[test]
fn cli_project_root_must_contain_entry_file() {
let root = setup_project(
"project_root_containment",
&[
("project/main.simf", "simc \"*\";\nfn main() {}\n"),
(
"other/placeholder.simf",
"simc \"*\";\nfn placeholder() {}\n",
),
],
);

let output = Command::new(env!("CARGO_BIN_EXE_simc"))
.arg(root.join("project/main.simf"))
.arg("--project-root")
.arg(root.join("other"))
.output()
.expect("failed to run simc");

assert!(
!output.status.success(),
"simc must reject an entry file outside the project root"
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("outside project root"),
"expected a project-root containment error, got:\n{stderr}"
);
}

#[test]
fn cli_root_level_entry_keeps_entry_parent_default() {
let root = setup_project(
"root_level_entry_default",
&[
(
"main.simf",
"simc \"*\";\nuse crate::utils::helper;\nfn main() { helper(); }\n",
),
("utils.simf", "simc \"*\";\npub fn helper() {}\n"),
],
);

let output = Command::new(env!("CARGO_BIN_EXE_simc"))
.arg(root.join("main.simf"))
.arg("-Z")
.arg("imports")
.output()
.expect("failed to run simc");

assert!(
output.status.success(),
"root-level entry failed\nstderr:\n{}",
String::from_utf8_lossy(&output.stderr),
);
}

#[test]
fn cli_import_program_rejected_without_unstable_flag() {
let root = repo_path("functional-tests/valid-test-cases/external-library-uses-crate");
Expand Down
Loading