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
3d6f280 1
#![allow(non_snake_case)]
3d6f280 2
use plum_core::loadAndMerge;
8a7f6ad 3
use std::fs;
8a7f6ad 4
use tempfile::TempDir;
8a7f6ad 5
8a7f6ad 6
/// Writes `name` (without a directory prefix — always placed directly under
8a7f6ad 7
/// the temp dir's `lib_path` subdirectory) with `contents`, creating parent
8a7f6ad 8
/// directories as needed (so `"std/option"` works).
3d6f280 9
fn writeLibFile(lib_path: &std::path::Path, name: &str, contents: &str) -> std::path::PathBuf {
8a7f6ad 10
    let path = lib_path.join(format!("{}.plum", name));
8a7f6ad 11
    fs::create_dir_all(path.parent().unwrap()).unwrap();
8a7f6ad 12
    fs::write(&path, contents).unwrap();
8a7f6ad 13
    path
8a7f6ad 14
}
8a7f6ad 15
8a7f6ad 16
#[test]
3d6f280 17
fn importSeesImportedDeclarations() {
8a7f6ad 18
    let dir = TempDir::new().unwrap();
8a7f6ad 19
    let lib_path = dir.path().join("libs");
3d6f280 20
    writeLibFile(&lib_path, "helper", "module fixtures\n\nfun helperFn() -> Int =\n  42\n");
3d6f280 21
    let entry = writeLibFile(&lib_path, "main", "module fixtures\n\nimport helper\n\nfun main() -> Int =\n  helperFn()\n");
8a7f6ad 22
3d6f280 23
    let merged = loadAndMerge(&entry, &lib_path).expect("loadAndMerge failed");
8a7f6ad 24
    let names: Vec<&str> = merged.items.iter().filter_map(|i| match i {
8a7f6ad 25
        plum_core::ast::Item::Fn(f) => Some(f.name.as_str()),
8a7f6ad 26
        _ => None,
8a7f6ad 27
    }).collect();
8a7f6ad 28
    assert!(names.contains(&"main"), "expected 'main' in merged items, got {:?}", names);
8a7f6ad 29
    assert!(names.contains(&"helperFn"), "expected 'helperFn' in merged items, got {:?}", names);
8a7f6ad 30
}
8a7f6ad 31
8a7f6ad 32
#[test]
3d6f280 33
fn transitiveImportSurfacesGrandchildDeclarations() {
8a7f6ad 34
    let dir = TempDir::new().unwrap();
8a7f6ad 35
    let lib_path = dir.path().join("libs");
3d6f280 36
    writeLibFile(&lib_path, "c", "module fixtures\n\nfun cFn() -> Int =\n  3\n");
3d6f280 37
    writeLibFile(&lib_path, "b", "module fixtures\n\nimport c\n\nfun bFn() -> Int =\n  cFn()\n");
3d6f280 38
    let entry = writeLibFile(&lib_path, "a", "module fixtures\n\nimport b\n\nfun main() -> Int =\n  bFn()\n");
8a7f6ad 39
3d6f280 40
    let merged = loadAndMerge(&entry, &lib_path).expect("loadAndMerge failed");
8a7f6ad 41
    let names: Vec<&str> = merged.items.iter().filter_map(|i| match i {
8a7f6ad 42
        plum_core::ast::Item::Fn(f) => Some(f.name.as_str()),
8a7f6ad 43
        _ => None,
8a7f6ad 44
    }).collect();
8a7f6ad 45
    assert!(names.contains(&"cFn"), "expected transitively-imported 'cFn', got {:?}", names);
8a7f6ad 46
}
8a7f6ad 47
8a7f6ad 48
#[test]
3d6f280 49
fn diamondImportIncludesSharedDependencyOnce() {
8a7f6ad 50
    let dir = TempDir::new().unwrap();
8a7f6ad 51
    let lib_path = dir.path().join("libs");
3d6f280 52
    writeLibFile(&lib_path, "d", "module fixtures\n\nfun dFn() -> Int =\n  4\n");
3d6f280 53
    writeLibFile(&lib_path, "b", "module fixtures\n\nimport d\n\nfun bFn() -> Int =\n  dFn()\n");
3d6f280 54
    writeLibFile(&lib_path, "c", "module fixtures\n\nimport d\n\nfun cFn() -> Int =\n  dFn()\n");
3d6f280 55
    let entry = writeLibFile(&lib_path, "a", "module fixtures\n\nimport b\nimport c\n\nfun main() -> Int =\n  bFn() + cFn()\n");
8a7f6ad 56
3d6f280 57
    let merged = loadAndMerge(&entry, &lib_path).expect("loadAndMerge failed");
8a7f6ad 58
    let d_count = merged.items.iter().filter(|i| matches!(i, plum_core::ast::Item::Fn(f) if f.name == "dFn")).count();
8a7f6ad 59
    assert_eq!(d_count, 1, "expected 'dFn' exactly once in a diamond import, got {}", d_count);
8a7f6ad 60
}
8a7f6ad 61
8a7f6ad 62
#[test]
3d6f280 63
fn importCycleResolvesWithoutHangingOrErroring() {
8a7f6ad 64
    let dir = TempDir::new().unwrap();
8a7f6ad 65
    let lib_path = dir.path().join("libs");
3d6f280 66
    writeLibFile(&lib_path, "b", "module fixtures\n\nimport a\n\nfun bFn() -> Int =\n  1\n");
3d6f280 67
    let entry = writeLibFile(&lib_path, "a", "module fixtures\n\nimport b\n\nfun main() -> Int =\n  bFn()\n");
8a7f6ad 68
3d6f280 69
    let merged = loadAndMerge(&entry, &lib_path).expect("import cycle should resolve, not error");
8a7f6ad 70
    let names: Vec<&str> = merged.items.iter().filter_map(|i| match i {
8a7f6ad 71
        plum_core::ast::Item::Fn(f) => Some(f.name.as_str()),
8a7f6ad 72
        _ => None,
8a7f6ad 73
    }).collect();
8a7f6ad 74
    assert!(names.contains(&"main"));
8a7f6ad 75
    assert!(names.contains(&"bFn"));
8a7f6ad 76
}
8a7f6ad 77
8a7f6ad 78
#[test]
3d6f280 79
fn unresolvableImportIsAClearError() {
8a7f6ad 80
    let dir = TempDir::new().unwrap();
8a7f6ad 81
    let lib_path = dir.path().join("libs");
3d6f280 82
    let entry = writeLibFile(&lib_path, "main", "module fixtures\n\nimport does_not_exist\n\nfun main() -> Int =\n  0\n");
8a7f6ad 83
3d6f280 84
    let result = loadAndMerge(&entry, &lib_path);
8a7f6ad 85
    assert!(result.is_err());
8a7f6ad 86
    let msg = result.unwrap_err();
8a7f6ad 87
    assert!(msg.contains("does_not_exist"), "expected error to name the missing import, got: {}", msg);
8a7f6ad 88
}
8a7f6ad 89
8a7f6ad 90
#[test]
3d6f280 91
fn duplicateTopLevelNameAcrossFilesIsAClearError() {
8a7f6ad 92
    let dir = TempDir::new().unwrap();
8a7f6ad 93
    let lib_path = dir.path().join("libs");
3d6f280 94
    writeLibFile(&lib_path, "helper", "module fixtures\n\nfun sharedFn() -> Int =\n  1\n");
3d6f280 95
    let entry = writeLibFile(&lib_path, "main", "module fixtures\n\nimport helper\n\nfun sharedFn() -> Int =\n  2\n");
8a7f6ad 96
3d6f280 97
    let result = loadAndMerge(&entry, &lib_path);
8a7f6ad 98
    assert!(result.is_err());
8a7f6ad 99
    let msg = result.unwrap_err();
8a7f6ad 100
    assert!(msg.contains("sharedFn"), "expected error to name the duplicate 'sharedFn', got: {}", msg);
8a7f6ad 101
}
8a7f6ad 102
8a7f6ad 103
#[test]
3d6f280 104
fn sameMethodNameOnDifferentReceiversAcrossFilesIsNotACollision() {
8a7f6ad 105
    let dir = TempDir::new().unwrap();
8a7f6ad 106
    let lib_path = dir.path().join("libs");
3d6f280 107
    writeLibFile(&lib_path, "cat", "\
8a7f6ad 108
module fixtures
8a7f6ad 109
8a7f6ad 110
type Cat =
8a7f6ad 111
  age: Int
8a7f6ad 112
2b8194c 113
  fun length(self) -> Int =
2b8194c 114
    self.age
8a7f6ad 115
");
3d6f280 116
    let entry = writeLibFile(&lib_path, "main", "\
8a7f6ad 117
module fixtures
8a7f6ad 118
8a7f6ad 119
import cat
8a7f6ad 120
8a7f6ad 121
type Box =
8a7f6ad 122
  items: Int
8a7f6ad 123
2b8194c 124
  fun length(self) -> Int =
2b8194c 125
    self.items
8a7f6ad 126
b48d3a3 127
fun main() -> Int =
8a7f6ad 128
  0
8a7f6ad 129
");
8a7f6ad 130
3d6f280 131
    let merged = loadAndMerge(&entry, &lib_path).expect("same method name on different receivers should not collide");
8a7f6ad 132
    let method_count = merged.items.iter().filter(|i| matches!(i, plum_core::ast::Item::Fn(f) if f.name == "length")).count();
8a7f6ad 133
    assert_eq!(method_count, 2, "expected both 'length' methods to survive the merge, got {}", method_count);
8a7f6ad 134
}