Bob/tests/test_classes_comprehensive.bob
Bobby Lucero 3138f6fb92 Various changes, again. Updated extension. Added classes, super, this, polymorphism.
Runtime: add method dispatch for array/string/dict/number (.len, .push, .pop, .keys, .values, .has, .toInt)
Stdlib: delete global len/push/pop/keys/values/has
Tests/docs/examples: migrate to method style; add tests/test_builtin_methods_style.bob
All tests pass
Breaking: global len/push/pop/keys/values/has removed; use methods instead
Parser/AST: add class/extends/extension/super, field initializers
Runtime: shared methods with this injection; classParents/classTemplates; super resolution; ownerClass/currentClass; extension lookup order
Builtins: method dispatch for array/string/dict/number (.len/.push/.pop/.keys/.values/.has/.toInt); remove global forms
Tests/docs/examples: add/refresh for classes, inheritance, super, polymorphism; migrate to method style; all tests pass
VS Code extension: update grammar/readme/snippets for new features
2025-08-10 22:44:46 -04:00

95 lines
3.0 KiB
Plaintext

print("\n--- Test: Classes Comprehensive ---");
// Basic class with field and method using this
class Person {
var name;
func init(n) { this.name = n; }
func greet() { return "Hello " + this.name; }
}
var p = Person("Bob");
assert(p.name == "Bob", "init sets name");
assert(p.greet() == "Hello Bob", "method with this works");
// Field initializers (constant)
class Box {
var width = 2;
var height = 3;
func area() { return this.width * this.height; }
}
var b = Box();
assert(b.width == 2, "field initializer width");
assert(b.height == 3, "field initializer height");
assert(b.area() == 6, "method sees initialized fields");
// Inline method precedence over extensions
class Animal {
var name;
func init(n) { this.name = n; }
}
extension Animal { func speak() { return this.name + "?"; } }
var a = Animal("crit");
assert(a.speak() == "crit?", "Animal.speak via extension");
class Dog extends Animal {
func speak() { return this.name + "!"; }
}
var d = Dog("fido");
assert(d.speak() == "fido!", "Dog inline speak overrides Animal extension");
// Inheritance chain for extensions: define on parent only
class Cat extends Animal {}
var c = Cat("mew");
assert(c.speak() == "mew?", "Cat inherits Animal.speak extension");
// Add extension later and ensure it applies retroactively
class Bird { var name; func init(n) { this.name = n; } }
var bird = Bird("tweet");
extension Bird { func speak() { return this.name + "~"; } }
assert(bird.speak() == "tweet~", "Late extension attaches to existing instances");
// any fallback extension when nothing else matches
extension any { func tag() { return "<" + toString(this) + ">"; } }
var plain = { "x": 1 };
assert(plain.tag() == "<{" + "\"x\": 1}" + ">", "any fallback extension works on dict");
// Ensure property value shadows extension lookup
class Shadow { }
extension Shadow { func value() { return 42; } }
var sh = Shadow();
sh.value = 7;
assert(sh.value == 7, "user property shadows extension method");
// Ensure instance method shadows extension
class Shadow2 { func m() { return 1; } }
extension Shadow2 { func m() { return 2; } }
var sh2 = Shadow2();
assert(sh2.m() == 1, "instance method shadows extension method");
// Method call injection of this; method reference does not auto-bind
class Ref {
var v = 5;
func get() { return this.v; }
}
var r = Ref();
var m = r.get;
// Calling m() should fail due to missing this; instead call through property again
assert(r.get() == 5, "method call via property injects this");
// Inheritance of inline methods
class Base { func id() { return 1; } }
class Child extends Base { }
var ch = Child();
assert(ch.id() == 1, "inherit inline method");
// Parent chain precedence: Child has no say(), extension on Base should work
extension Base { func say() { return "base"; } }
assert(ch.say() == "base", "inherit extension from parent");
// Verify __class tag exists for instances (check with method that reads this)
assert(Ref().get() == 5, "instance constructed correctly");
print("Classes comprehensive: PASS");