plum

#treesitter#compiler#wasm

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);
}