Files
Brummel 8aa2a67b5b Introduce StaticType::Variadic for variadic parameters
This commit introduces `StaticType::Variadic` to represent variadic
parameter types in function signatures. This allows for more precise
type checking of functions that accept a variable number of arguments,
such as arithmetic operators.

Previously, variadic functions were handled using `StaticType::Any`,
which was too permissive and could lead to runtime errors. The new
`Variadic` type enforces that all arguments must conform to a specified
inner type.

Changes include:

- Adding `StaticType::Variadic` to `src/ast/types.rs`.
- Updating the type checker to handle `Variadic` types correctly when
  unifying with tuples or single arguments.
- Modifying built-in functions like arithmetic operators to use
  `Variadic` for their parameter types.
- Improving error messages for function call mismatches to be more
  specific.
- Adding a new test case to ensure arithmetic type mismatches are caught
  at compile time.
- A new example `data_stream_pipe_buffered.myc` is added.
- The `HMA.myc` example now has a `Skip: output` directive.
2026-03-30 11:36:26 +02:00

124 lines
3.2 KiB
Rust

use myc::ast::environment::Environment;
#[test]
fn test_record_basics() {
let env = Environment::new();
let source = r#"
((fn [user] [(.name user) (.age user)])
{:name "Alice" :age 30})
"#;
let res = env.run_script(source).unwrap();
assert_eq!(format!("{}", res), "[\"Alice\" 30]");
}
#[test]
fn test_record_optimized_access() {
let env = Environment::new();
let source_eval = "(.price {:id 1 :price 99.5})";
let res = env.run_script(source_eval).unwrap();
assert_eq!(format!("{}", res), "99.5");
// Verify optimization to GET_FIELD when the record contains non-constants
let source_ast = "(fn [id] (.price {:id id :price 99.5}))";
let dump = env.dump_ast(source_ast).unwrap();
assert!(
dump.contains("GetField: .price"),
"Should be optimized to GetField. Dump:\n{}",
dump
);
}
#[test]
fn test_first_class_field_accessor() {
let env = Environment::new();
let source = r#"
(do
(def get-name .name)
(get-name {:name "Alice"}))
"#;
let res = env.run_script(source).unwrap();
assert_eq!(format!("{}", res), "\"Alice\"");
}
#[test]
fn test_record_constant_folding() {
let env = Environment::new();
// Optimizer should fold (.x {:x 10}) into 10
let source = "(.x {:x 10 :y 20})";
let dump = env.dump_ast(source).unwrap();
assert!(
dump.contains("Constant: 10"),
"Should evaluate GetField at compile time."
);
}
#[test]
fn test_record_literal_constant_folding() {
let env = Environment::new();
let source = " {:a 1 :b 2} ";
let dump = env.dump_ast(source).unwrap();
assert!(
dump.contains("Constant: {:a 1, :b 2}"),
"Should transform a pure record literal into a constant value."
);
assert!(
!dump.contains("Record {"),
"Should not leave a runtime Record node in the AST."
);
}
#[test]
fn test_record_inlining_in_while_loop() {
let env_ast = Environment::new();
let source = r#"
(do
(def loop-config {:start 0 :limit 10})
(def loop-idx 0)
(while (< loop-idx (.limit loop-config))
(assign loop-idx (+ loop-idx 1)))
loop-idx
)
"#;
let dump = env_ast.dump_ast(source).unwrap();
assert!(
dump.contains("Constant: 10") || dump.contains("GetField: .limit"),
"The limit should be resolved to a constant 10 or kept as GetField."
);
let env_run = Environment::new();
let res = env_run.run_script(source).unwrap();
assert_eq!(format!("{}", res), "10");
}
#[test]
fn test_record_errors() {
let env = Environment::new();
// 1. Missing field
let res_missing = env.run_script("(.missing {:a 1})");
assert!(res_missing.is_err());
assert!(res_missing.unwrap_err().contains("no matching overload"));
// 2. Not a record
let res_not_rec = env.run_script("(.name 123)");
assert!(res_not_rec.is_err());
assert!(res_not_rec.unwrap_err().contains("no matching overload"));
}
#[test]
fn test_record_layout_interning() {
let env = Environment::new();
let source = r#"
(do
(def r1 {:a 1 :b 2})
(def r2 {:a 10 :b 20})
(= r1 r2))
"#;
let res = env.run_script(source).unwrap();
assert_eq!(format!("{}", res), "false");
}