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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

## [fix] — 2026-03-17
- Added array methods: `.length()` and `.push()`
- Allowed typed empty array assignment like `var xs: [int] = []`
- Added `compiler/v1/src/tests/array_methods.rey` regression test

## [init] — 2026-03-17
- Claude initialized as contributor
- Created CLAUDE.md and primer.md
Expand Down
13 changes: 13 additions & 0 deletions compiler/v1/src/interpreter/executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,19 @@ impl Executor {

fn evaluate_method_call(&self, receiver: Value, name: &str, args: &[Value]) -> Result<Value, String> {
match (receiver, name) {
(Value::Array(arr), "length") => {
if !args.is_empty() {
return Err(format!("{}.length() expects 0 arguments, got {}", "Array", args.len()));
}
Ok(Value::Number(arr.borrow().len() as f64))
}
(Value::Array(arr), "push") => {
if args.len() != 1 {
return Err(format!("{}.push() expects 1 argument, got {}", "Array", args.len()));
}
arr.borrow_mut().push(args[0].clone());
Ok(Value::Null)
}
(Value::String(s), "length") => {
if !args.is_empty() {
return Err(format!("{}.length() expects 0 arguments, got {}", "String", args.len()));
Expand Down
16 changes: 16 additions & 0 deletions compiler/v1/src/tests/array_methods.rey
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Array method support

func main(): Void {
var ints: [int] = [1, 2, 3];
println(ints.length());

var any = [];
any.push(1);
any.push("a");
println(any.length());

var result: [int] = [];
result.push(1);
result.push(2);
println(result.length());
}
26 changes: 25 additions & 1 deletion compiler/v1/src/typecheck.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,12 @@ impl TypeChecker {
let initTy = self.exprTy(initializer)?;
let finalTy = if let Some(ann) = ty {
let annTy = Ty::fromAnnotation(ann)?;
if !initTy.isAssignableTo(&annTy) {
let allowEmptyTypedArray = matches!(
(initializer, &initTy, &annTy),
(Expr::ArrayLiteral { elements }, Ty::Array(inner), Ty::Array(_))
if elements.is_empty() && matches!(inner.as_ref(), Ty::Any)
);
if !initTy.isAssignableTo(&annTy) && !allowEmptyTypedArray {
return Err(format!(
"Type error: variable '{}' expected {:?} but got {:?}",
name, annTy, initTy
Expand Down Expand Up @@ -393,6 +398,25 @@ impl TypeChecker {

fn methodTy(&mut self, receiver: &Ty, name: &str, args: &[Expr]) -> Result<Ty, String> {
match (receiver, name) {
(Ty::Array(_), "length") => {
if !args.is_empty() {
return Err("Type error: Array.length() expects 0 arguments".to_string());
}
Ok(Ty::Int)
}
(Ty::Array(inner), "push") => {
if args.len() != 1 {
return Err("Type error: Array.push() expects 1 argument".to_string());
}
let a0 = self.exprTy(&args[0])?;
if !a0.isAssignableTo(inner.as_ref()) {
return Err(format!(
"Type error: Array.push() expected element {:?} but got {:?}",
inner, a0
));
}
Ok(Ty::Void)
}
(Ty::String, "length") | (Ty::String, "upper") | (Ty::String, "lower") => {
if !args.is_empty() {
return Err(format!("Type error: String.{}() expects 0 arguments", name));
Expand Down
1 change: 1 addition & 0 deletions primer.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ cargo run -- src/tests/variables.rey
- Functions with optional typed params and return types
- Builtins: println(), len(), push(), pop(), input()
- Arrays: literals, indexing, typed arrays ([int])
- Array methods: length(), push()
- Dictionaries: literals, indexing, typed dicts ({String:int})
- String methods: length/upper/lower/contains/split
- Property access: obj.prop (dictionary key lookup)
Expand Down
Loading