Commit f11ced15 authored by Roman Alifanov's avatar Roman Alifanov

Fix inherited method resolution, array field returns, cli.ct help

- DCE: propagate parent methods to child classes so inherited proxy functions are generated (StringFlag.with_short etc.) - stmt_codegen: return this.field.method() now checks field type (array/dict) instead of always assuming string - stmt_codegen: fix missing f-prefix on two RET_VAR f-strings - cli.ct: exit(0) after --help/--version to prevent double output - Add TestInheritance tests covering all fixed bugs
parent a63b495a
......@@ -132,6 +132,19 @@ class UsageAnalyzer:
self.used_methods[cls_decl.parent].add(method)
changed = True
for cls_name in list(self.used_classes):
if cls_name in self.defined_classes:
cls_decl = self.defined_classes[cls_name]
if cls_decl.parent and cls_decl.parent in self.used_methods:
child_methods = {m.name for m in cls_decl.methods}
for method in list(self.used_methods[cls_decl.parent]):
if method not in child_methods:
if cls_name not in self.used_methods:
self.used_methods[cls_name] = set()
if method not in self.used_methods[cls_name]:
self.used_methods[cls_name].add(method)
changed = True
def get_used_classes(self) -> set:
return self.used_classes
......
......@@ -436,7 +436,7 @@ class StmtMixin:
args = [self.generate_expr(arg) for arg in stmt.value.arguments]
args_str = " ".join([f'"{a}"' for a in args])
self.emit(f'{stmt.value.class_name} {args_str}')
self.emit('{RET_VAR}="$__ct_last_instance"')
self.emit(f'{RET_VAR}="$__ct_last_instance"')
self.emit(f'echo "${{{RET_VAR}}}"')
self.emit("return 0")
return
......@@ -512,13 +512,35 @@ class StmtMixin:
method = expr.callee.member
args = [self.generate_expr(arg) for arg in expr.arguments]
args_str = " ".join([f'"{a}"' for a in args])
field_type = self.class_field_types.get((self.current_class, field)) if self.current_class else None
arr_methods = {
"len": "__ct_arr_len", "push": "__ct_arr_push", "pop": "__ct_arr_pop",
"shift": "__ct_arr_shift", "join": "__ct_arr_join", "get": "__ct_arr_get",
"set": "__ct_arr_set", "slice": "__ct_arr_slice",
}
dict_methods = {
"get": "__ct_dict_get", "set": "__ct_dict_set", "has": "__ct_dict_has",
"del": "__ct_dict_del", "keys": "__ct_dict_keys",
}
str_methods = {
"len": "__ct_str_len", "upper": "__ct_str_upper", "lower": "__ct_str_lower",
"trim": "__ct_str_trim", "contains": "__ct_str_contains", "starts": "__ct_str_starts",
"ends": "__ct_str_ends", "index": "__ct_str_index", "replace": "__ct_str_replace",
"substr": "__ct_str_substr", "split": "__ct_str_split", "charAt": "__ct_str_char_at",
}
if method in str_methods:
if field_type == "array" and method in arr_methods:
func_name = arr_methods[method]
self.emit(f'{func_name} "${{this}}_{field}" {args_str} >/dev/null'.replace(' ', ' '))
self.emit(f'echo "${{{RET_VAR}}}"')
self.emit("return 0")
return True
elif field_type == "dict" and method in dict_methods:
func_name = dict_methods[method]
self.emit(f'{func_name} "${{this}}_{field}" {args_str} >/dev/null'.replace(' ', ' '))
self.emit(f'echo "${{{RET_VAR}}}"')
self.emit("return 0")
return True
elif method in str_methods:
func_name = str_methods[method]
self.emit(f'{func_name} "${{__CT_OBJ["$this.{field}"]}}" {args_str} >/dev/null'.replace(' ', ' '))
self.emit(f'echo "${{{RET_VAR}}}"')
......@@ -547,7 +569,7 @@ class StmtMixin:
args = [self._generate_call_arg(arg) for arg in expr.arguments]
args_str = " ".join([f'"{a}"' for a in args])
self.emit(f'{func_name} {args_str}')
self.emit('{RET_VAR}="$__ct_last_instance"')
self.emit(f'{RET_VAR}="$__ct_last_instance"')
self.emit(f'echo "${{{RET_VAR}}}"')
self.emit("return 0")
return True
......
......@@ -336,7 +336,7 @@ class Command {
h = this.bool ("help")
if h {
this.help ()
return ""
exit (0)
}
}
......@@ -346,7 +346,7 @@ class Command {
v = this.bool ("version")
if v {
print ("{this.name} v{this.version}")
return ""
exit (0)
}
}
......
......@@ -290,6 +290,97 @@ print (c.value)
assert "20" in stdout
class TestInheritance:
def test_inherited_method_via_factory(self):
code, stdout, _ = run_ct('''
class Animal {
name = ""
construct (name) {
this.name = name
}
func speak () {
return "..."
}
}
class Dog : Animal {
construct (name) {
base (name)
}
}
func make_dog (name) {
return new Dog (name)
}
d = make_dog ("Rex")
print (d.speak ())
''')
assert code == 0
assert "..." in stdout
def test_inherited_method_direct(self):
code, stdout, _ = run_ct('''
class Base {
func hello () {
return "hello from base"
}
}
class Child : Base {
construct () {}
}
c = new Child ()
print (c.hello ())
''')
assert code == 0
assert "hello from base" in stdout
def test_array_field_len_in_method(self):
code, stdout, _ = run_ct('''
class Bag {
items = []
func count () {
return this.items.len ()
}
}
func main () {
b = new Bag ()
b.items.push ("a")
b.items.push ("b")
print (b.count ())
}
main ()
''')
assert code == 0
assert "2" in stdout
def test_array_field_get_in_method(self):
code, stdout, _ = run_ct('''
class Stack {
data = []
func top () {
n = this.data.len ()
return this.data.get (n - 1)
}
}
func main () {
s = new Stack ()
s.data.push ("first")
s.data.push ("second")
print (s.top ())
}
main ()
''')
assert code == 0
assert "second" in stdout
class TestDictFieldAssignment:
def test_dict_field_assign_simple(self):
code, stdout, stderr = run_ct('''
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment