Commit 3cfa569f authored by Roman Alifanov's avatar Roman Alifanov

Fix chained method calls on object fields (obj.field.method())

parent 41f07888
......@@ -669,11 +669,10 @@ def _method_stmt(node: IRMethodCall, ctx: 'EmitContext') -> None:
return
args = _ct_args(node.args, ctx)
_DICT_ONLY_M_ = {'has', 'del', 'keys'}
is_field_recv_ = isinstance(node.receiver, IRFieldAccess)
is_param_array = recv_name in ctx.param_array_vars
is_actual_array = recv_name in ctx.array_vars
is_field_array = is_field_recv_ and method not in _DICT_ONLY_M_
is_field_array = is_field_recv_ and node.receiver.type.kind == 'array'
if is_param_array or is_actual_array or is_field_array:
arr_ref = recv if (is_param_array or is_field_array) else f'"{recv_name}"'
ctx.emit(f'{ARR_PREFIX}{method} {arr_ref} {args}'.strip())
......@@ -681,7 +680,7 @@ def _method_stmt(node: IRMethodCall, ctx: 'EmitContext') -> None:
is_param_dict = recv_name in ctx.param_dict_vars
is_actual_dict = recv_name in ctx.dict_vars
is_field_dict = is_field_recv_ and method in _DICT_ONLY_M_
is_field_dict = is_field_recv_ and node.receiver.type.kind == 'dict'
if is_param_dict or is_actual_dict or is_field_dict:
dict_ref = recv if (is_param_dict or is_field_dict) else f'"{recv_name}"'
ctx.emit(f'{DICT_PREFIX}{method} {dict_ref} {args}'.strip())
......@@ -753,13 +752,11 @@ def _stdlib_method_expr(node: IRMethodCall, ctx: 'EmitContext') -> str:
is_known_array = node.receiver.type.kind == 'array'
is_actual_array = recv_name in ctx.array_vars # actual array var — pass by name
is_param_array = recv_name in ctx.param_array_vars # param holding array name — pass "${p}"
# Field access holding array name — unambiguous array methods, or ambiguous (heuristic: default array)
_DICT_ONLY_M = {'has', 'del', 'keys'}
is_field_array = is_field_recv and method not in _DICT_ONLY_M
is_field_array = is_field_recv and node.receiver.type.kind == 'array'
is_known_dict = node.receiver.type.kind == 'dict'
is_actual_dict = recv_name in ctx.dict_vars
is_param_dict = recv_name in ctx.param_dict_vars
is_field_dict = is_field_recv and method in _DICT_ONLY_M
is_field_dict = is_field_recv and node.receiver.type.kind == 'dict'
# arr.len() fast path — only for statically-typed or actual local arrays (not field access)
if method == 'len' and (is_known_array or is_actual_array) and not is_field_recv:
......@@ -831,12 +828,11 @@ def _stdlib_method_stmt(node: IRMethodCall, ctx: 'EmitContext') -> None:
is_known_array = node.receiver.type.kind == 'array'
is_actual_array = recv_name in ctx.array_vars
is_param_array = recv_name in ctx.param_array_vars
_DICT_ONLY_M = {'has', 'del', 'keys'}
is_field_array = is_field_recv and method not in _DICT_ONLY_M
is_field_array = is_field_recv and node.receiver.type.kind == 'array'
is_known_dict = node.receiver.type.kind == 'dict'
is_actual_dict = recv_name in ctx.dict_vars
is_param_dict = recv_name in ctx.param_dict_vars
is_field_dict = is_field_recv and method in _DICT_ONLY_M
is_field_dict = is_field_recv and node.receiver.type.kind == 'dict'
# Fast-path mutations only for statically-typed or local arrays (not params or field access)
if (is_known_array or is_actual_array) and not is_field_recv:
......@@ -892,11 +888,10 @@ def _instance_method_expr(node: IRMethodCall, ctx: 'EmitContext') -> str:
if result is not None:
return result
_DICT_ONLY_M = {'has', 'del', 'keys'}
is_field_recv = isinstance(node.receiver, IRFieldAccess)
is_param_array = recv_name in ctx.param_array_vars
is_actual_array = recv_name in ctx.array_vars
is_field_array = is_field_recv and method not in _DICT_ONLY_M
is_field_array = is_field_recv and node.receiver.type.kind == 'array'
if is_param_array or is_actual_array or is_field_array:
arr_ref = recv if (is_param_array or is_field_array) else f'"{recv_name}"'
fn = f'{ARR_PREFIX}{method}'
......@@ -909,7 +904,7 @@ def _instance_method_expr(node: IRMethodCall, ctx: 'EmitContext') -> str:
is_param_dict = recv_name in ctx.param_dict_vars
is_actual_dict = recv_name in ctx.dict_vars
is_field_dict = is_field_recv and method in _DICT_ONLY_M
is_field_dict = is_field_recv and node.receiver.type.kind == 'dict'
if is_param_dict or is_actual_dict or is_field_dict:
dict_ref = recv if (is_param_dict or is_field_dict) else f'"{recv_name}"'
fn = f'{DICT_PREFIX}{method}'
......
......@@ -681,13 +681,37 @@ class IRBuilder:
if isinstance(node.object, Identifier) and node.object.name == 'env':
return IRIdentifier(name=f'env.{node.member}', type=T_STRING, source=loc)
recv = self._build_expr(node.object) if node.object else IRNil()
field_type = T_ANY
cls_name = None
if isinstance(node.object, ThisExpr) and self._current_class:
cls_name = self._current_class
elif isinstance(recv, IRIdentifier) and recv.symbol and recv.symbol.type.kind == 'class':
cls_name = recv.symbol.type.class_name
if cls_name:
cls_scope = self._find_class_scope(cls_name)
if cls_scope:
f_sym = cls_scope.symbols.get(node.member)
if f_sym:
field_type = f_sym.type
return IRFieldAccess(receiver=recv, field_name=node.member,
type=T_ANY, source=loc)
type=field_type, source=loc)
# ------------------------------------------------------------------
# Scope helpers
# ------------------------------------------------------------------
def _find_class_scope(self, class_name: str) -> Optional[Scope]:
for child in self.global_scope.children:
if child.kind == 'class' and child.name == class_name:
return child
if child.kind == 'namespace':
for gc in child.children:
if gc.kind == 'class' and gc.name == class_name:
return gc
return None
def _resolve_name(self, name: str) -> Optional[Symbol]:
sym = self._scope.lookup(name)
if sym: return sym
......
......@@ -147,9 +147,15 @@ class Resolver:
cls_scope = scope.child('class', name=node.name, owner=sym)
for field in node.fields:
f_type = T_ANY
if field.default:
if isinstance(field.default, ArrayLiteral):
f_type = array_of(T_ANY)
elif isinstance(field.default, DictLiteral):
f_type = dict_of(T_ANY, T_ANY)
f_sym = Symbol(
name=field.name, kind='field',
type=T_ANY, decl=field, defined_at=field.location,
type=f_type, decl=field, defined_at=field.location,
)
cls_scope.define(f_sym)
......@@ -217,6 +223,8 @@ class Resolver:
ann_type = array_of(T_ANY)
elif isinstance(node.value, DictLiteral):
ann_type = dict_of(T_ANY, T_ANY)
elif isinstance(node.value, NewExpr):
ann_type = class_type(node.value.class_name)
var_sym = Symbol(
name=node.target.name,
kind='var',
......
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