Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
C
ContenT
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Ximper Linux
ContenT
Commits
3cfa569f
Commit
3cfa569f
authored
Feb 23, 2026
by
Roman Alifanov
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Fix chained method calls on object fields (obj.field.method())
parent
41f07888
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
42 additions
and
15 deletions
+42
-15
expr.py
compiler/backend/bash/expr.py
+8
-13
builder.py
compiler/ir/builder.py
+25
-1
resolver.py
compiler/semantics/resolver.py
+9
-1
No files found.
compiler/backend/bash/expr.py
View file @
3cfa569f
...
...
@@ -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}'
...
...
compiler/ir/builder.py
View file @
3cfa569f
...
...
@@ -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
...
...
compiler/semantics/resolver.py
View file @
3cfa569f
...
...
@@ -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'
,
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment