Skip to content

Commit e57dae9

Browse files
tests: +33 across canonical / classes / exceptions
test_canonical_extras.omc (11): explicit-parens preservation, multi-fn order, nested-fn canonicalization, recursive-call name preservation, try/catch err-var rename, class-method param rename, lambda param rename, arithmetic-through-rename, indentation/newline invariance, inline-comment stripping, empty-body. test_classes_extras.omc (11): basic construction, method dispatch, self.field access, inheritance override (Dog.speak vs Animal.speak), inherited method (Animal.name_str), two-level chain (Puppy → Dog → Animal), is_instance self / parent / grandparent / unrelated / non-instance. test_exceptions_extras.omc (11): try-no-throw, basic throw/catch, finally on success and on failure, dict / array typed exceptions, nested inner/outer catch, rethrow propagation, throw across function boundary, catch by class via is_instance, most-specific class wins. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 7f3d279 commit e57dae9

3 files changed

Lines changed: 299 additions & 0 deletions

File tree

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# More canonical / structural-equivalence tests.
2+
3+
fn assert_eq(actual, expected, msg) {
4+
if actual != expected {
5+
test_record_failure(msg + ": expected " + to_string(expected) + " got " + to_string(actual));
6+
}
7+
}
8+
9+
fn assert_true(cond, msg) { if !cond { test_record_failure(msg); } }
10+
11+
# Operator precedence preserved by canonical (parens are explicit)
12+
fn test_canonical_parens_explicit() {
13+
h c1 = omc_code_canonical("h x = a + b * c;");
14+
h c2 = omc_code_canonical("h x = a + (b * c);");
15+
# Canonical inserts explicit parens, so both should match.
16+
assert_eq(c1, c2, "canonical adds explicit parens");
17+
}
18+
19+
# Multiple functions preserve order.
20+
fn test_canonical_multi_fn_order() {
21+
h c1 = omc_code_canonical("fn a() { return 1; } fn b() { return 2; }");
22+
h c2 = omc_code_canonical("fn a() { return 1; } fn b() { return 2; }");
23+
assert_eq(c1, c2, "order preserved");
24+
}
25+
26+
# Nested function definitions canonicalize independently.
27+
fn test_canonical_nested_fn() {
28+
h c1 = omc_code_canonical("fn outer() { fn inner(x) { return x; } return inner(1); }");
29+
h c2 = omc_code_canonical("fn outer() { fn inner(y) { return y; } return inner(1); }");
30+
assert_eq(c1, c2, "nested params normalize");
31+
}
32+
33+
# Recursive calls preserve name.
34+
fn test_canonical_recursive() {
35+
h c1 = omc_code_canonical("fn fact(n) { if n <= 1 { return 1; } return n * fact(n - 1); }");
36+
h c2 = omc_code_canonical("fn fact(m) { if m <= 1 { return 1; } return m * fact(m - 1); }");
37+
assert_eq(c1, c2, "recursive call name preserved");
38+
}
39+
40+
# Try/catch/finally invariance.
41+
fn test_canonical_try_catch() {
42+
h c1 = omc_code_canonical("fn f() { try { return 1; } catch e { return 0; } }");
43+
h c2 = omc_code_canonical("fn f() { try { return 1; } catch err { return 0; } }");
44+
assert_eq(c1, c2, "catch var renamed");
45+
}
46+
47+
# Class definitions canonicalize methods independently.
48+
fn test_canonical_class_methods() {
49+
h c1 = omc_code_canonical("class P { x; fn get(self) { return dict_get(self, \"x\"); } }");
50+
h c2 = omc_code_canonical("class P { x; fn get(this) { return dict_get(this, \"x\"); } }");
51+
assert_eq(c1, c2, "method params canonicalize");
52+
}
53+
54+
# Lambda invariance.
55+
fn test_canonical_lambda_in_call() {
56+
h c1 = omc_code_canonical("h ys = arr_map(xs, fn(v) { return v * 2; });");
57+
h c2 = omc_code_canonical("h ys = arr_map(xs, fn(x) { return x * 2; });");
58+
assert_eq(c1, c2, "lambda param renamed");
59+
}
60+
61+
# Match canonical with bind patterns (skipped — parser doesn't accept return
62+
# directly as arm body in this position; tested elsewhere).
63+
64+
# Arithmetic semantics preserved.
65+
fn test_canonical_arith() {
66+
h c1 = omc_code_canonical("fn f(x) { return x * 2 + 1; }");
67+
h c2 = omc_code_canonical("fn f(y) { return y * 2 + 1; }");
68+
assert_eq(c1, c2, "rename through arithmetic");
69+
70+
h c3 = omc_code_canonical("fn f(x) { return x * 3 + 1; }");
71+
assert_true(c1 != c3, "literal change matters");
72+
}
73+
74+
# Multi-line whitespace differences.
75+
fn test_canonical_indentation() {
76+
h c1 = omc_code_canonical("fn f(x) {\n return x;\n}");
77+
h c2 = omc_code_canonical("fn f(x){\nreturn x;\n}");
78+
h c3 = omc_code_canonical("fn f(x) {return x;}");
79+
assert_eq(c1, c2, "indentation ignored");
80+
assert_eq(c1, c3, "newlines ignored");
81+
}
82+
83+
# Comments stripped consistently.
84+
fn test_canonical_inline_comments() {
85+
h c1 = omc_code_canonical("fn f(x) { return x; }");
86+
h c2 = omc_code_canonical("fn f(x) { # comment\n return x; # trailing\n}");
87+
assert_eq(c1, c2, "inline comments stripped");
88+
}
89+
90+
# Empty function body.
91+
fn test_canonical_empty_fn() {
92+
h c1 = omc_code_canonical("fn empty() {}");
93+
h c2 = omc_code_canonical("fn empty() {\n\n\n}");
94+
assert_eq(c1, c2, "empty body");
95+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# More class / inheritance tests.
2+
3+
fn assert_eq(actual, expected, msg) {
4+
if actual != expected {
5+
test_record_failure(msg + ": expected " + to_string(expected) + " got " + to_string(actual));
6+
}
7+
}
8+
9+
fn assert_true(cond, msg) { if !cond { test_record_failure(msg); } }
10+
11+
class Animal {
12+
name;
13+
fn speak(self) { return "generic"; }
14+
fn name_str(self) { return dict_get(self, "name"); }
15+
}
16+
17+
class Dog extends Animal {
18+
name;
19+
fn speak(self) { return "woof"; }
20+
}
21+
22+
class Puppy extends Dog {
23+
name;
24+
}
25+
26+
# Basic construction
27+
fn test_class_construction() {
28+
h a = Animal("zoe");
29+
assert_eq(dict_get(a, "name"), "zoe", "name field set");
30+
}
31+
32+
fn test_class_method_call() {
33+
h a = Animal("zoe");
34+
assert_eq(a.speak(), "generic", "Animal speaks generic");
35+
}
36+
37+
fn test_class_method_self_access() {
38+
h a = Animal("zoe");
39+
assert_eq(a.name_str(), "zoe", "method reads self.name");
40+
}
41+
42+
# Inheritance: child overrides parent
43+
fn test_inheritance_override() {
44+
h d = Dog("rex");
45+
assert_eq(d.speak(), "woof", "Dog overrides speak");
46+
}
47+
48+
fn test_inheritance_inherits_method() {
49+
h d = Dog("rex");
50+
# Inherits name_str from Animal.
51+
assert_eq(d.name_str(), "rex", "inherits Animal.name_str");
52+
}
53+
54+
# Two-level inheritance
55+
fn test_inheritance_two_levels() {
56+
h p = Puppy("tiny");
57+
# Puppy inherits Dog.speak (which overrode Animal.speak).
58+
assert_eq(p.speak(), "woof", "two-level dispatch");
59+
# And Puppy inherits Animal.name_str transitively.
60+
assert_eq(p.name_str(), "tiny", "transitive inheritance");
61+
}
62+
63+
# is_instance walks the chain
64+
fn test_is_instance_self() {
65+
h d = Dog("x");
66+
assert_eq(is_instance(d, "Dog"), 1, "is Dog");
67+
}
68+
69+
fn test_is_instance_parent() {
70+
h d = Dog("x");
71+
assert_eq(is_instance(d, "Animal"), 1, "is Animal too");
72+
}
73+
74+
fn test_is_instance_grandparent() {
75+
h p = Puppy("x");
76+
assert_eq(is_instance(p, "Animal"), 1, "Puppy is Animal");
77+
assert_eq(is_instance(p, "Dog"), 1, "Puppy is Dog");
78+
assert_eq(is_instance(p, "Puppy"), 1, "Puppy is Puppy");
79+
}
80+
81+
fn test_is_instance_unrelated() {
82+
h p = Puppy("x");
83+
assert_eq(is_instance(p, "Cat"), 0, "Puppy is not Cat");
84+
}
85+
86+
fn test_is_instance_non_instance() {
87+
assert_eq(is_instance(42, "Anything"), 0, "int is not class instance");
88+
assert_eq(is_instance("hello", "Anything"), 0, "string is not class instance");
89+
assert_eq(is_instance([1, 2], "Anything"), 0, "array is not class instance");
90+
}
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
# More exception / try-catch / typed-throw tests.
2+
3+
fn assert_eq(actual, expected, msg) {
4+
if actual != expected {
5+
test_record_failure(msg + ": expected " + to_string(expected) + " got " + to_string(actual));
6+
}
7+
}
8+
9+
fn assert_true(cond, msg) { if !cond { test_record_failure(msg); } }
10+
11+
# Basic try/catch flow
12+
fn test_try_no_throw() {
13+
h ran = 0;
14+
try { ran = 1; } catch e { ran = 99; }
15+
assert_eq(ran, 1, "no throw → no catch");
16+
}
17+
18+
fn test_try_with_throw() {
19+
h caught = "";
20+
try { throw "boom"; } catch e { caught = e; }
21+
assert_eq(caught, "boom", "caught");
22+
}
23+
24+
# Finally always runs
25+
fn test_finally_runs_on_success() {
26+
h ran = 0;
27+
try { } catch e { ran = 99; } finally { ran = 1; }
28+
assert_eq(ran, 1, "finally ran on success");
29+
}
30+
31+
fn test_finally_runs_on_failure() {
32+
h c = 0;
33+
h f = 0;
34+
try { throw "x"; } catch e { c = 1; } finally { f = 1; }
35+
assert_eq(c, 1, "catch ran");
36+
assert_eq(f, 1, "finally ran");
37+
}
38+
39+
# Typed exceptions
40+
fn test_dict_exception() {
41+
h kind = "";
42+
try {
43+
h err = dict_new();
44+
dict_set(err, "kind", "FileNotFound");
45+
throw err;
46+
} catch e {
47+
kind = dict_get(e, "kind");
48+
}
49+
assert_eq(kind, "FileNotFound", "dict kind preserved");
50+
}
51+
52+
fn test_array_exception() {
53+
h len = 0;
54+
try { throw [1, 2, 3, 4]; } catch e { len = arr_len(e); }
55+
assert_eq(len, 4, "array preserved");
56+
}
57+
58+
# Nested try
59+
fn test_nested_inner_catches() {
60+
h inner = 0;
61+
h outer = 0;
62+
try {
63+
try { throw "x"; } catch e { inner = 1; }
64+
} catch e { outer = 99; }
65+
assert_eq(inner, 1, "inner ran");
66+
assert_eq(outer, 0, "outer didn't run");
67+
}
68+
69+
fn test_nested_rethrow_to_outer() {
70+
h outer_v = "";
71+
try {
72+
try { throw "first"; } catch e { throw "rethrow"; }
73+
} catch e { outer_v = e; }
74+
assert_eq(outer_v, "rethrow", "outer sees rethrow");
75+
}
76+
77+
# Throw across function boundary
78+
fn raises_value(v) { throw v; }
79+
80+
fn test_throw_across_call() {
81+
h caught = 0;
82+
try { raises_value(42); } catch e { caught = e; }
83+
assert_eq(caught, "42", "stringified");
84+
}
85+
86+
# Catch by class via is_instance
87+
class AppError {
88+
message;
89+
}
90+
class HttpError extends AppError {
91+
message;
92+
status;
93+
}
94+
95+
fn test_catch_by_class() {
96+
h handled = 0;
97+
try { throw AppError("oops"); }
98+
catch e {
99+
if is_instance(e, "AppError") { handled = 1; }
100+
}
101+
assert_eq(handled, 1, "AppError caught");
102+
}
103+
104+
fn test_catch_subclass_as_parent() {
105+
h handled_as = "";
106+
try { throw HttpError("404", 404); }
107+
catch e {
108+
if is_instance(e, "HttpError") { handled_as = "http"; }
109+
if handled_as == "" {
110+
if is_instance(e, "AppError") { handled_as = "app"; }
111+
}
112+
}
113+
assert_eq(handled_as, "http", "most specific wins");
114+
}

0 commit comments

Comments
 (0)