plum
git clone https://git.pyrossh.dev/plum
A statically typed, imperative programming language inspired by rust, python
plum-core/tests/loader_test.rs
#![allow(non_snake_case)]
use plum_core::loadAndMerge;
use std::fs;
use tempfile::TempDir;
/// Writes `name` (without a directory prefix — always placed directly under
/// the temp dir's `lib_path` subdirectory) with `contents`, creating parent
/// directories as needed (so `"std/option"` works).
fn writeLibFile(lib_path: &std::path::Path, name: &str, contents: &str) -> std::path::PathBuf {
let path = lib_path.join(format!("{}.plum", name));
fs::create_dir_all(path.parent().unwrap()).unwrap();
fs::write(&path, contents).unwrap();
path
}
#[test]
fn importSeesImportedDeclarations() {
let dir = TempDir::new().unwrap();
let lib_path = dir.path().join("libs");
writeLibFile(&lib_path, "helper", "module fixtures\n\nfun helperFn() -> Int =\n 42\n");
let entry = writeLibFile(&lib_path, "main", "module fixtures\n\nimport helper\n\nfun main() -> Int =\n helperFn()\n");
let merged = loadAndMerge(&entry, &lib_path).expect("loadAndMerge failed");
let names: Vec<&str> = merged.items.iter().filter_map(|i| match i {
plum_core::ast::Item::Fn(f) => Some(f.name.as_str()),
_ => None,
}).collect();
assert!(names.contains(&"main"), "expected 'main' in merged items, got {:?}", names);
assert!(names.contains(&"helperFn"), "expected 'helperFn' in merged items, got {:?}", names);
}
#[test]
fn transitiveImportSurfacesGrandchildDeclarations() {
let dir = TempDir::new().unwrap();
let lib_path = dir.path().join("libs");
writeLibFile(&lib_path, "c", "module fixtures\n\nfun cFn() -> Int =\n 3\n");
writeLibFile(&lib_path, "b", "module fixtures\n\nimport c\n\nfun bFn() -> Int =\n cFn()\n");
let entry = writeLibFile(&lib_path, "a", "module fixtures\n\nimport b\n\nfun main() -> Int =\n bFn()\n");
let merged = loadAndMerge(&entry, &lib_path).expect("loadAndMerge failed");
let names: Vec<&str> = merged.items.iter().filter_map(|i| match i {
plum_core::ast::Item::Fn(f) => Some(f.name.as_str()),
_ => None,
}).collect();
assert!(names.contains(&"cFn"), "expected transitively-imported 'cFn', got {:?}", names);
}
#[test]
fn diamondImportIncludesSharedDependencyOnce() {
let dir = TempDir::new().unwrap();
let lib_path = dir.path().join("libs");
writeLibFile(&lib_path, "d", "module fixtures\n\nfun dFn() -> Int =\n 4\n");
writeLibFile(&lib_path, "b", "module fixtures\n\nimport d\n\nfun bFn() -> Int =\n dFn()\n");
writeLibFile(&lib_path, "c", "module fixtures\n\nimport d\n\nfun cFn() -> Int =\n dFn()\n");
let entry = writeLibFile(&lib_path, "a", "module fixtures\n\nimport b\nimport c\n\nfun main() -> Int =\n bFn() + cFn()\n");
let merged = loadAndMerge(&entry, &lib_path).expect("loadAndMerge failed");
let d_count = merged.items.iter().filter(|i| matches!(i, plum_core::ast::Item::Fn(f) if f.name == "dFn")).count();
assert_eq!(d_count, 1, "expected 'dFn' exactly once in a diamond import, got {}", d_count);
}
#[test]
fn importCycleResolvesWithoutHangingOrErroring() {
let dir = TempDir::new().unwrap();
let lib_path = dir.path().join("libs");
writeLibFile(&lib_path, "b", "module fixtures\n\nimport a\n\nfun bFn() -> Int =\n 1\n");
let entry = writeLibFile(&lib_path, "a", "module fixtures\n\nimport b\n\nfun main() -> Int =\n bFn()\n");
let merged = loadAndMerge(&entry, &lib_path).expect("import cycle should resolve, not error");
let names: Vec<&str> = merged.items.iter().filter_map(|i| match i {
plum_core::ast::Item::Fn(f) => Some(f.name.as_str()),
_ => None,
}).collect();
assert!(names.contains(&"main"));
assert!(names.contains(&"bFn"));
}
#[test]
fn unresolvableImportIsAClearError() {
let dir = TempDir::new().unwrap();
let lib_path = dir.path().join("libs");
let entry = writeLibFile(&lib_path, "main", "module fixtures\n\nimport does_not_exist\n\nfun main() -> Int =\n 0\n");
let result = loadAndMerge(&entry, &lib_path);
assert!(result.is_err());
let msg = result.unwrap_err();
assert!(msg.contains("does_not_exist"), "expected error to name the missing import, got: {}", msg);
}
#[test]
fn duplicateTopLevelNameAcrossFilesIsAClearError() {
let dir = TempDir::new().unwrap();
let lib_path = dir.path().join("libs");
writeLibFile(&lib_path, "helper", "module fixtures\n\nfun sharedFn() -> Int =\n 1\n");
let entry = writeLibFile(&lib_path, "main", "module fixtures\n\nimport helper\n\nfun sharedFn() -> Int =\n 2\n");
let result = loadAndMerge(&entry, &lib_path);
assert!(result.is_err());
let msg = result.unwrap_err();
assert!(msg.contains("sharedFn"), "expected error to name the duplicate 'sharedFn', got: {}", msg);
}
#[test]
fn sameMethodNameOnDifferentReceiversAcrossFilesIsNotACollision() {
let dir = TempDir::new().unwrap();
let lib_path = dir.path().join("libs");
writeLibFile(&lib_path, "cat", "\
module fixtures
type Cat =
age: Int
fun length(self) -> Int =
self.age
");
let entry = writeLibFile(&lib_path, "main", "\
module fixtures
import cat
type Box =
items: Int
fun length(self) -> Int =
self.items
fun main() -> Int =
0
");
let merged = loadAndMerge(&entry, &lib_path).expect("same method name on different receivers should not collide");
let method_count = merged.items.iter().filter(|i| matches!(i, plum_core::ast::Item::Fn(f) if f.name == "length")).count();
assert_eq!(method_count, 2, "expected both 'length' methods to survive the merge, got {}", method_count);
}