refactor(viperc): 完全删除 T1 | T2 类型组合,语法糖改用标记下标

This commit is contained in:
TermiNexus committed 2026-10-18 13:39:38 +08:00
1 parent 42b094a575
commit 82a735c367
12 files changed
+355 -463

No files matched your search

+14 -1
View File
@@ -212,7 +212,20 @@ def _PyiTypeStr(buf: bytes, buf_size: sizet, pos: sizet, annot: ast.AST*, depth:
if at.attr is not None: pos = _PyiEmit(buf, buf_size, pos, at.attr)
return pos
# Subscript: subscripted type (e.g. list[int], t.CArray[elem, count])
# Call: attribute marker with arguments (e.g. attr.aligned(16), attr.section(".x"))
if k == ast.ASTKind.Call:
cl_t: ast.Call* = ast.Call*(annot)
if cl_t.func is None: return pos
pos = _PyiTypeStr(buf, buf_size, pos, cl_t.func, depth + 1)
pos = _PyiEmit(buf, buf_size, pos, "(")
if cl_t.args is not None:
for ai_t in range(int(len(cl_t.args))):
if ai_t > 0: pos = _PyiEmit(buf, buf_size, pos, ", ")
pos = _PyiTypeStr(buf, buf_size, pos, cl_t.args.get(ai_t), depth + 1)
pos = _PyiEmit(buf, buf_size, pos, ")")
return pos
# Subscript: subscripted type (e.g. list[int], CArray[elem, count])
if k == ast.ASTKind.Subscript:
sub: ast.Subscript* = ast.Subscript*(annot)
pos = _PyiTypeStr(buf, buf_size, pos, sub.value, depth + 1)
+45
View File
@@ -1631,6 +1631,49 @@ def _resolve_single_vpi_type_str(pool: memhub.MemBuddy*, seg: str) -> mlirlite.L
# vpi caches `-> None` verbatim as "None"; map None to Void so cross-module
# `-> None` methods do not fail fast with an unresolvable return type.
if string.strcmp(seg, "None") == 0: return mlirlite.Void(pool)
# Descriptor / storage / value-reference marker wrappers: CConst[T], State[T], CExport[T], ...
# The marker carries no value type, so resolve the wrapped type.
if seg_len_ps > 0 and seg[seg_len_ps - 1] == ']':
lb_mk: str = string.strchr(seg, '[')
if lb_mk is not None:
lb_off: sizet = sizet(uint64(lb_mk) - uint64(seg))
if lb_off > 0 and seg_len_ps > lb_off + 1:
mk_len: sizet = lb_off
mk_buf: bytes = stdlib.malloc(mk_len + 1)
if mk_buf is not None:
string.strncpy(mk_buf, seg, mk_len)
mk_buf[mk_len] = '\0'
mk_name: str = mk_buf
# `attr.<name>` / `attr.<name>(...)` markers are builtin descriptors.
is_attr_marker: int = 0
if string.strncmp(mk_name, "attr.", 5) == 0 or string.strcmp(mk_name, "attr") == 0: is_attr_marker = 1
dot_mk: str = string.strrchr(mk_name, '.')
if dot_mk is not None: mk_name = dot_mk + 1
is_marker: int = is_attr_marker
if string.strcmp(mk_name, "CConst") == 0 or string.strcmp(mk_name, "CVolatile") == 0 \
or string.strcmp(mk_name, "BigEndian") == 0 or string.strcmp(mk_name, "LittleEndian") == 0 \
or string.strcmp(mk_name, "CStatic") == 0 or string.strcmp(mk_name, "CExport") == 0 \
or string.strcmp(mk_name, "CExtern") == 0 or string.strcmp(mk_name, "State") == 0 \
or string.strcmp(mk_name, "CDefine") == 0 or string.strcmp(mk_name, "CRef") == 0 \
or string.strcmp(mk_name, "CTempRef") == 0 or string.strcmp(mk_name, "CAutoPtr") == 0 \
or string.strcmp(mk_name, "attr") == 0:
is_marker = 1
if is_marker != 0:
inner_len_mk: sizet = seg_len_ps - lb_off - 2
inner_mk: bytes = stdlib.malloc(inner_len_mk + 1)
if inner_mk is not None:
string.strncpy(inner_mk, seg + lb_off + 1, inner_len_mk)
inner_mk[inner_len_mk] = '\0'
inner_ty_mk: mlirlite.LLVMType* = _resolve_single_vpi_type_str(pool, inner_mk)
stdlib.free(inner_mk)
# Value-reference parameters use the pointer ABI: &T / &temp T -> T*.
if inner_ty_mk is not None and (string.strcmp(mk_name, "CRef") == 0 or string.strcmp(mk_name, "CTempRef") == 0):
inner_ty_mk = mlirlite.Ptr(pool, inner_ty_mk)
elif string.strcmp(mk_name, "CAutoPtr") == 0:
inner_ty_mk = i8_ptr_ty
stdlib.free(mk_buf)
return inner_ty_mk
stdlib.free(mk_buf)
# rawptr[T] built-in raw pointer (equivalent to t.CPtr[T] -> Ptr(T)); vpi keeps the
# Subscript form verbatim. Same as TypeGenResolve's rawptr branch: Ptr(resolve(T)).
if string.strncmp(seg, "rawptr[", 7) == 0:
@@ -1697,6 +1740,8 @@ def _resolve_single_vpi_type_str(pool: memhub.MemBuddy*, seg: str) -> mlirlite.L
def _resolve_vpi_ret_annot_str(pool: memhub.MemBuddy*, ret_annot: str, i32_ty: mlirlite.LLVMType*, i8_ptr_ty: mlirlite.LLVMType*) -> mlirlite.LLVMType*:
"""Resolve a vpi return-annotation string directly to LLVMType without building AST nodes."""
if ret_annot is None or pool is None: return None
# `T1 | T2` unions were retired; a stub containing one must be regenerated.
if string.strchr(ret_annot, '|') is not None: return None
# tuple[T1, T2, ...] -> anonymous struct {T1, T2, ...} (multi-return packing),
# matching TypeGenMixin._resolve_generic_subscript_ty's tuple branch.
+94 -73
View File
@@ -25,17 +25,18 @@ novirtual class AssignGenMixin:
# Check whether an annotation is the CDefine marker (module-level function)
def is_cdefine_annotation(self, annot: ast.AST*) -> int:
"""Return 1 if the annotation is the CDefine marker, else 0."""
"""Return 1 if the annotation is the CDefine marker (bare or CDefine[T]), else 0."""
if annot is None: return 0
k: int = annot.kind()
if k == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(annot)
if nm.id is not None and string.strcmp(nm.id, "CDefine") == 0: return 1
return 0
if k == ast.ASTKind.BinOp:
bop: ast.BinOp* = ast.BinOp*(annot)
if self.is_cdefine_annotation(bop.left) != 0: return 1
if self.is_cdefine_annotation(bop.right) != 0: return 1
if k == ast.ASTKind.Subscript:
sub_cd: ast.Subscript* = ast.Subscript*(annot)
if sub_cd.value is not None and sub_cd.value.kind() == ast.ASTKind.Name:
mk_cd: ast.Name* = ast.Name*(sub_cd.value)
if mk_cd.id is not None and string.strcmp(mk_cd.id, "CDefine") == 0: return 1
return 0
# extract_cdefine_int_value - extract an integer constant from AnnAssign.value
@@ -325,22 +326,28 @@ novirtual class AssignGenMixin:
self.Gen.TypeGenErr.fatal_error(node, f"[VPC] shift count out of range: the count {v} is >= the left operand width {lhs_bits} bits; " f"the result would be 0 or undefined (not a value the compiler may guess). " f"Fix: use a count < {lhs_bits}, or widen the left operand.", "ValueError")
def _is_cdefine_marker_node(self, node: ast.AST*) -> int:
"""Return 1 if the node is the CDefine marker node."""
"""Return 1 if the node is the CDefine marker node (bare or CDefine[T])."""
if node is None: return 0
k: int = node.kind()
if k == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(node)
if nm.id is not None and string.strcmp(nm.id, "CDefine") == 0: return 1
if k == ast.ASTKind.Subscript:
sub_cm: ast.Subscript* = ast.Subscript*(node)
if sub_cm.value is not None and sub_cm.value.kind() == ast.ASTKind.Name:
mk_cm: ast.Name* = ast.Name*(sub_cm.value)
if mk_cm.id is not None and string.strcmp(mk_cm.id, "CDefine") == 0: return 1
return 0
def _define_explicit_annot(self, annot: ast.AST*) -> ast.AST*:
"""Return the explicit type T of a `T | t.CDefine` annotation, or None."""
"""Return the explicit type T of a `CDefine[T]` annotation, or None."""
if annot is None: return None
if annot.kind() != ast.ASTKind.BinOp: return None
bop: ast.BinOp* = ast.BinOp*(annot)
if bop.left is None: return None
if self._is_cdefine_marker_node(bop.left) != 0: return None
return bop.left
if annot.kind() != ast.ASTKind.Subscript: return None
sub_d: ast.Subscript* = ast.Subscript*(annot)
if sub_d.value is None or sub_d.value.kind() != ast.ASTKind.Name: return None
mk_d: ast.Name* = ast.Name*(sub_d.value)
if mk_d.id is None or string.strcmp(mk_d.id, "CDefine") != 0: return None
return sub_d.slice
def _annot_int_bits(self, pool: memhub.MemBuddy*, annot: ast.AST*) -> int:
"""Return the bit width of an integer annotation, or 0 if unknown."""
@@ -353,6 +360,9 @@ novirtual class AssignGenMixin:
elif k == ast.ASTKind.Name:
n2: ast.Name* = ast.Name*(annot)
nm = n2.id
elif k == ast.ASTKind.Subscript:
# Descriptor wrapper (CConst[T] etc.): the width comes from the wrapped type.
return self._annot_int_bits(pool, (ast.Subscript*(annot)).slice)
if nm is None: return 0
return self._int_bits_of_type_name(pool, nm, 0)
@@ -422,6 +432,8 @@ novirtual class AssignGenMixin:
elif k == ast.ASTKind.Name:
n2: ast.Name* = ast.Name*(annot)
nm = n2.id
elif k == ast.ASTKind.Subscript:
return self._annot_float_kind(pool, (ast.Subscript*(annot)).slice)
if nm is None: return 0
return self._float_kind_of_type_name(pool, nm, 0)
@@ -910,28 +922,20 @@ novirtual class AssignGenMixin:
nm: ast.Name* = ast.Name*(annot)
if nm.id is not None and string.strcmp(nm.id, name) == 0: return 1
return 0
if k == ast.ASTKind.BinOp:
bop: ast.BinOp* = ast.BinOp*(annot)
if self._annotation_contains_name(bop.left, name) != 0: return 1
if self._annotation_contains_name(bop.right, name) != 0: return 1
if k == ast.ASTKind.Subscript:
sub_cn: ast.Subscript* = ast.Subscript*(annot)
if sub_cn.slice is not None and self._annotation_contains_name(sub_cn.slice, name) != 0: return 1
return 0
def _annotation_has_cptr_marker(self, annot: ast.AST*) -> int:
"""Return 1 if the annotation union carries a CPtr marker (the star form `<base> | rawptr`)."""
"""Return 1 if the annotation is a CPtr pointer wrapper (the `T*` form)."""
if annot is None: return 0
k: int = annot.kind()
if k == ast.ASTKind.Attribute:
at: ast.Attribute* = ast.Attribute*(annot)
if at.attr is not None and string.strcmp(at.attr, "CPtr") == 0: return 1
return 0
if k == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(annot)
if nm.id is not None and string.strcmp(nm.id, "CPtr") == 0: return 1
return 0
if k == ast.ASTKind.BinOp:
bop: ast.BinOp* = ast.BinOp*(annot)
if self._annotation_has_cptr_marker(bop.left) != 0: return 1
if self._annotation_has_cptr_marker(bop.right) != 0: return 1
if annot.kind() != ast.ASTKind.Subscript: return 0
sub_cp: ast.Subscript* = ast.Subscript*(annot)
if sub_cp.value is not None and sub_cp.value.kind() == ast.ASTKind.Name:
nm_cp: ast.Name* = ast.Name*(sub_cp.value)
if nm_cp.id is not None and (string.strcmp(nm_cp.id, "CPtr") == 0 or string.strcmp(nm_cp.id, "rawptr") == 0): return 1
if sub_cp.slice is not None: return self._annotation_has_cptr_marker(sub_cp.slice)
return 0
# _is_ptr_element_annotation - check whether an annotation is bytes* or str*
@@ -939,34 +943,35 @@ novirtual class AssignGenMixin:
def _is_ptr_element_annotation(self, annot: ast.AST*) -> int:
"""Return 1 if the annotation is a bytes* or str* form."""
if annot is None: return 0
k: int = annot.kind()
if k != ast.ASTKind.BinOp: return 0
bop: ast.BinOp* = ast.BinOp*(annot)
if bop.op != ast.OpKind.BitOr: return 0
# str/bytes are already i8*; only the union WITH a pointer marker (str*/bytes*) is i8** and
# needs 8-byte element slots. A union that merely carries them (e.g. `const str`, which is
# t.CConst | str and stays i8*) must keep the 1-byte stride.
if self._annotation_has_cptr_marker(annot) != 0:
if self._annotation_contains_name(annot, "str") != 0: return 1
if self._annotation_contains_name(annot, "bytes") != 0: return 1
# Generic type param / nested union | t.CPtr: scalars use their own size, pointers use 8-byte slots
if self._is_generic_tp_or_nested(bop.left) != 0 or self._is_generic_tp_or_nested(bop.right) != 0:
gp_nm: str = None
if bop.left is not None and bop.left.kind() == ast.ASTKind.Name:
gp_left_nm: ast.Name* = ast.Name*(bop.left)
gp_nm = gp_left_nm.id
elif bop.right is not None and bop.right.kind() == ast.ASTKind.Name:
gp_right_nm: ast.Name* = ast.Name*(bop.right)
gp_nm = gp_right_nm.id
if annot.kind() != ast.ASTKind.Subscript: return 0
sub_pe: ast.Subscript* = ast.Subscript*(annot)
if sub_pe.value is None or sub_pe.value.kind() != ast.ASTKind.Name: return 0
pe_nm: ast.Name* = ast.Name*(sub_pe.value)
if pe_nm.id is None: return 0
# Descriptor / storage / ref wrapper: the pointer form lives in the wrapped type.
if string.strcmp(pe_nm.id, "CConst") == 0 or string.strcmp(pe_nm.id, "CVolatile") == 0 \
or string.strcmp(pe_nm.id, "BigEndian") == 0 or string.strcmp(pe_nm.id, "LittleEndian") == 0 \
or string.strcmp(pe_nm.id, "CStatic") == 0 or string.strcmp(pe_nm.id, "CExport") == 0 \
or string.strcmp(pe_nm.id, "CExtern") == 0 or string.strcmp(pe_nm.id, "State") == 0 \
or string.strcmp(pe_nm.id, "CDefine") == 0 or string.strcmp(pe_nm.id, "CRef") == 0 \
or string.strcmp(pe_nm.id, "CTempRef") == 0 or string.strcmp(pe_nm.id, "CAutoPtr") == 0:
return self._is_ptr_element_annotation(sub_pe.slice)
if string.strcmp(pe_nm.id, "CPtr") != 0 and string.strcmp(pe_nm.id, "rawptr") != 0: return 0
inner_pe: ast.AST* = sub_pe.slice
if inner_pe is None: return 0
# str/bytes are already i8*; the pointer layer makes them i8** and needs 8-byte slots.
if self._annotation_contains_name(inner_pe, "str") != 0: return 1
if self._annotation_contains_name(inner_pe, "bytes") != 0: return 1
# Generic type param: a scalar-typed param uses its own size, a pointer/aggregate uses 8 bytes.
if inner_pe.kind() == ast.ASTKind.Name:
gp_nm: str = (ast.Name*(inner_pe)).id
if gp_nm is not None:
# Resolve the generic param via the global gen (direct field access breaks static binding)
gp_resolved: str = self.Gen.TypeGenBase.resolve_generic_type_name(gp_nm)
if gp_resolved is not None and string.strcmp(gp_resolved, gp_nm) != 0:
# Generic param resolved to a scalar => use scalar size, not an 8-byte pointer slot
if self._is_scalar_ty_name(gp_resolved) != 0: return 0
return 1
# 64-bit scalar | CPtr: element is 8 bytes, so use an 8-byte stride (else GEP truncation)
if self._is_64bit_scalar_ty(bop.left) != 0 or self._is_64bit_scalar_ty(bop.right) != 0: return 1
if gp_resolved is not None: return 1
# 64-bit scalar pointee: element is 8 bytes (else GEP truncation).
if self._is_64bit_scalar_ty(inner_pe) != 0: return 1
return 0
def _stg_ty_equal(self, pool: memhub.MemBuddy*, ta: mlirlite.LLVMType*, tb: mlirlite.LLVMType*) -> int:
@@ -2420,14 +2425,16 @@ novirtual class AssignGenMixin:
def _prespecialize_generic_annotation(self, trans: HT.Translator*, pool: memhub.MemBuddy*, annot: ast.AST*) -> int:
"""Pre-specialize a generic annotation; return 1 if attempted."""
if annot is None or trans is None: return 0
# Take the generic Subscript side: the Subscript itself or the Subscript of A*
# Take the generic Subscript side: the Subscript itself or the Subscript wrapped by CPtr[T].
ann_ps: ast.AST* = annot
if ann_ps.kind() == ast.ASTKind.Subscript:
sub_wrap_ps: ast.Subscript* = ast.Subscript*(ann_ps)
if sub_wrap_ps.value is not None and sub_wrap_ps.value.kind() == ast.ASTKind.Name:
wk_nm_ps: ast.Name* = ast.Name*(sub_wrap_ps.value)
if wk_nm_ps.id is not None and (string.strcmp(wk_nm_ps.id, "CPtr") == 0 or string.strcmp(wk_nm_ps.id, "rawptr") == 0):
if sub_wrap_ps.slice is not None: ann_ps = sub_wrap_ps.slice
spec_annot: ast.Subscript* = None
if annot.kind() == ast.ASTKind.Subscript: spec_annot = ast.Subscript*(annot)
elif annot.kind() == ast.ASTKind.BinOp:
bop_ps: ast.BinOp* = ast.BinOp*(annot)
if bop_ps.op == ast.OpKind.BitOr:
if bop_ps.left is not None and bop_ps.left.kind() == ast.ASTKind.Subscript: spec_annot = ast.Subscript*(bop_ps.left)
elif bop_ps.right is not None and bop_ps.right.kind() == ast.ASTKind.Subscript: spec_annot = ast.Subscript*(bop_ps.right)
if ann_ps.kind() == ast.ASTKind.Subscript: spec_annot = ast.Subscript*(ann_ps)
if spec_annot is None: return 0
sv_ps: ast.AST* = spec_annot.value
if sv_ps is None or sv_ps.kind() != ast.ASTKind.Name: return 0
@@ -2444,14 +2451,16 @@ novirtual class AssignGenMixin:
def _fail_on_degenerated_generic_annotation(self, aa: ast.AnnAssign*, var_name: str, annot: ast.AST*, var_ty: mlirlite.LLVMType*) -> int:
"""Fail fast when a generic annotation degrades to a bare base pointer."""
if aa is None or annot is None or var_ty is None: return 0
# Take the generic Subscript side: the Subscript itself or the Subscript of A*
# Take the generic Subscript side: the Subscript itself or the Subscript wrapped by CPtr[T].
ann_ff: ast.AST* = annot
if ann_ff.kind() == ast.ASTKind.Subscript:
sub_wrap_ff: ast.Subscript* = ast.Subscript*(ann_ff)
if sub_wrap_ff.value is not None and sub_wrap_ff.value.kind() == ast.ASTKind.Name:
wk_nm_ff: ast.Name* = ast.Name*(sub_wrap_ff.value)
if wk_nm_ff.id is not None and (string.strcmp(wk_nm_ff.id, "CPtr") == 0 or string.strcmp(wk_nm_ff.id, "rawptr") == 0):
if sub_wrap_ff.slice is not None: ann_ff = sub_wrap_ff.slice
ann_sub: ast.Subscript* = None
if annot.kind() == ast.ASTKind.Subscript: ann_sub = ast.Subscript*(annot)
elif annot.kind() == ast.ASTKind.BinOp:
bop_ff: ast.BinOp* = ast.BinOp*(annot)
if bop_ff.op == ast.OpKind.BitOr:
if bop_ff.left is not None and bop_ff.left.kind() == ast.ASTKind.Subscript: ann_sub = ast.Subscript*(bop_ff.left)
elif bop_ff.right is not None and bop_ff.right.kind() == ast.ASTKind.Subscript: ann_sub = ast.Subscript*(bop_ff.right)
if ann_ff.kind() == ast.ASTKind.Subscript: ann_sub = ast.Subscript*(ann_ff)
if ann_sub is None or ann_sub.value is None or ann_sub.value.kind() != ast.ASTKind.Name: return 0
base_nm_ff: ast.Name* = ast.Name*(ann_sub.value)
if base_nm_ff.id is None: return 0
@@ -2833,11 +2842,13 @@ novirtual class AssignGenMixin:
ann_gen_base: str = None
if annot is not None:
ag_side: ast.AST* = annot
if ag_side.kind() == ast.ASTKind.BinOp:
ag_bop: ast.BinOp* = ast.BinOp*(ag_side)
if ag_bop.op == ast.OpKind.BitOr:
if ag_bop.left is not None and ag_bop.left.kind() == ast.ASTKind.Subscript: ag_side = ag_bop.left
elif ag_bop.right is not None and ag_bop.right.kind() == ast.ASTKind.Subscript: ag_side = ag_bop.right
# Unwrap a CPtr[T] pointer layer to inspect the generic base.
if ag_side.kind() == ast.ASTKind.Subscript:
ag_wrap: ast.Subscript* = ast.Subscript*(ag_side)
if ag_wrap.value is not None and ag_wrap.value.kind() == ast.ASTKind.Name:
ag_wnm: ast.Name* = ast.Name*(ag_wrap.value)
if ag_wnm.id is not None and (string.strcmp(ag_wnm.id, "CPtr") == 0 or string.strcmp(ag_wnm.id, "rawptr") == 0):
if ag_wrap.slice is not None: ag_side = ag_wrap.slice
if ag_side.kind() == ast.ASTKind.Subscript:
ag_sub: ast.Subscript* = ast.Subscript*(ag_side)
if ag_sub.value is not None and ag_sub.value.kind() == ast.ASTKind.Name:
@@ -3692,6 +3703,11 @@ novirtual class AssignGenMixin:
if seglen > 0 and seglen < 256:
string.strncpy(seg, annot + s, seglen)
seg[seglen] = '\0'
# Unwrap the CPtr[T] pointer marker: the class name is inside.
while seglen > 6 and string.strncmp(seg, "CPtr[", 5) == 0 and seg[seglen - 1] == ']':
seg = seg + 5
seglen -= 6
seg[seglen] = '\0'
se: GenTypes.StructEntry* = self.Gen.StructGen.find_struct(seg)
if se is not None: return seg
start = i + 1
@@ -3705,6 +3721,11 @@ novirtual class AssignGenMixin:
if seglen > 0 and seglen < 256:
string.strncpy(seg, annot + s, seglen)
seg[seglen] = '\0'
# Unwrap the CPtr[T] pointer marker: the class name is inside.
while seglen > 6 and string.strncmp(seg, "CPtr[", 5) == 0 and seg[seglen - 1] == ']':
seg = seg + 5
seglen -= 6
seg[seglen] = '\0'
se: GenTypes.StructEntry* = self.Gen.StructGen.find_struct(seg)
if se is not None: return seg
return None
+9 -8
View File
@@ -1059,15 +1059,16 @@ novirtual class CallGenCtor(CallGenBase.CallGenBase):
if trans.CurAssignNode is not None and uint64(cl) == uint64(trans.CurAssignNode): ctx_annot = trans.CurAssignAnnot
if ctx_annot is None and trans.CurRetNode is not None and uint64(cl) == uint64(trans.CurRetNode): ctx_annot = trans.CurFuncRetAnnot
if ctx_annot is None: return None
# Take the generic Subscript side of the annotation (same as _prespecialize_generic_annotation:
# supports both `X[T]` and `X[T]*`).
# Take the generic Subscript side of the annotation: `X[T]` or the `CPtr[X[T]]` pointer form.
ctx_ps: ast.AST* = ctx_annot
if ctx_ps.kind() == ast.ASTKind.Subscript:
sub_wrap_ctx: ast.Subscript* = ast.Subscript*(ctx_ps)
if sub_wrap_ctx.value is not None and sub_wrap_ctx.value.kind() == ast.ASTKind.Name:
wk_nm_ctx: ast.Name* = ast.Name*(sub_wrap_ctx.value)
if wk_nm_ctx.id is not None and (string.strcmp(wk_nm_ctx.id, "CPtr") == 0 or string.strcmp(wk_nm_ctx.id, "rawptr") == 0):
if sub_wrap_ctx.slice is not None: ctx_ps = sub_wrap_ctx.slice
spec_annot: ast.Subscript* = None
if ctx_annot.kind() == ast.ASTKind.Subscript: spec_annot = ast.Subscript*(ctx_annot)
elif ctx_annot.kind() == ast.ASTKind.BinOp:
bop_ctx: ast.BinOp* = ast.BinOp*(ctx_annot)
if bop_ctx.op == ast.OpKind.BitOr:
if bop_ctx.left is not None and bop_ctx.left.kind() == ast.ASTKind.Subscript: spec_annot = ast.Subscript*(bop_ctx.left)
elif bop_ctx.right is not None and bop_ctx.right.kind() == ast.ASTKind.Subscript: spec_annot = ast.Subscript*(bop_ctx.right)
if ctx_ps.kind() == ast.ASTKind.Subscript: spec_annot = ast.Subscript*(ctx_ps)
if spec_annot is None: return None
# The annotation must be the same generic class as this construction (otherwise unrelated)
sv_ctx: ast.AST* = spec_annot.value
+2 -10
View File
@@ -324,16 +324,8 @@ novirtual class CallGenMixin(CallGenBase.CallGenBase):
viperlib.snprintf(gt_err, 256, "Generic class '%s': template not found; the defining module may not be imported", gen_class_name)
self.Gen.TypeGenErr.fatal_error(gen_nm_node, gt_err)
self.Gen.TypeGenErr.fatal_error(gen_nm_node, "Generic class template not found")
if func_name is None:
# BinOp as the Call func: a union-type annotation used as a type cast, (A*)(value).
if cl.func is not None and cl.func.kind() == ast.ASTKind.BinOp and trans is not None:
binop_ty: mlirlite.LLVMType* = self.Gen.TypeGenResolve.resolve_annotation_type(pool, cl.func, trans._imported_modules, trans._from_imports, trans, 0)
if binop_ty is not None:
binop_args: list[ast.AST*]* = cl.args
if binop_args is not None and len(binop_args) == 1:
binop_arg_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, binop_args.get(0), funcs_ptr, func_count, trans)
if binop_arg_val is not None: return self.Gen.CallGenCtor._translate_t_type_cast(pool, builder, binop_arg_val, binop_ty)
return None
# Union type casts were retired; only Subscript / Name callees remain.
if func_name is None: return None
# c.Asm/c.LLVMIR special calls return None when void but are handled, so use hit_box to
# separate "missed"; otherwise they fall through to the external-function path and mis-report.
+20 -24
View File
@@ -71,23 +71,24 @@ novirtual class ClassGenEnum(ClassGenBase.ClassGenBase):
self.Gen.TypeGenErr.fatal_error(ann_node, f"failed to resolve enum member type annotation: {nm.id}", "NameError")
return mlirlite.Int32(pool) # unreachable (fatal_error already exited)
# BinOp node: t.State | int8 (recurse to find the concrete type)
# Marker wrapper (CConst[T] / CVolatile[T] / ...): resolve the wrapped type.
if k == ast.ASTKind.Subscript:
sub_em: ast.Subscript* = ast.Subscript*(ann_node)
if sub_em.value is not None and sub_em.value.kind() == ast.ASTKind.Name:
em_nm: ast.Name* = ast.Name*(sub_em.value)
if em_nm.id is not None and (string.strcmp(em_nm.id, "CConst") == 0 or string.strcmp(em_nm.id, "CVolatile") == 0 \
or string.strcmp(em_nm.id, "BigEndian") == 0 or string.strcmp(em_nm.id, "LittleEndian") == 0 \
or string.strcmp(em_nm.id, "State") == 0 or string.strcmp(em_nm.id, "CStatic") == 0 \
or string.strcmp(em_nm.id, "CExport") == 0 or string.strcmp(em_nm.id, "CExtern") == 0 \
or string.strcmp(em_nm.id, "CRef") == 0 or string.strcmp(em_nm.id, "CTempRef") == 0):
return self._resolve_enum_member_type(pool, sub_em.slice, imported_modules, from_imports)
self.Gen.TypeGenErr.fatal_type_error(ann_node, "unsupported enum member type: only scalar types and their markers are allowed")
return mlirlite.Int32(pool)
# BinOp nodes are never enum member types (`T1 | T2` unions retired).
if k == ast.ASTKind.BinOp:
bop: ast.BinOp* = ast.BinOp*(ann_node)
if bop.op == ast.OpKind.BitOr:
left_ty: mlirlite.LLVMType* = self._resolve_enum_member_type(pool, bop.left, imported_modules, from_imports)
# Skip t.State (the i32 default); prefer a concrete type
left_bits: int = self.Gen.TypeGenErr.get_llvm_type_bits(left_ty)
if left_bits != 0 and not self._is_state_annotation(bop.left): return left_ty
right_ty: mlirlite.LLVMType* = self._resolve_enum_member_type(pool, bop.right, imported_modules, from_imports)
if right_ty is not None and not self._is_state_annotation(bop.right): return right_ty
# Both sides are State or undecidable; return the wider one
right_bits: int = self.Gen.TypeGenErr.get_llvm_type_bits(right_ty)
if left_bits >= right_bits: return left_ty
return right_ty
# fail-fast: BinOp but not BitOr (unknown union op); no silent i32 fallback
self.Gen.TypeGenErr.fatal_error(ann_node, f"failed to resolve enum member type annotation: {self.Gen.TypeGenResolve.annotation_to_str(ann_node)}", "TypeError")
return mlirlite.Int32(pool) # unreachable (fatal_error already exited)
self.Gen.TypeGenErr.fatal_type_error(ann_node, "enum member type uses the retired `T1 | T2` union; write the concrete type directly")
return mlirlite.Int32(pool)
# fail-fast: unknown annotation node kind; no silent i32 fallback
self.Gen.TypeGenErr.fatal_error(ann_node, f"failed to resolve enum member type annotation: {self.Gen.TypeGenResolve.annotation_to_str(ann_node)}", "TypeError")
@@ -133,17 +134,12 @@ novirtual class ClassGenEnum(ClassGenBase.ClassGenBase):
return 0
if k == ast.ASTKind.Subscript:
sub: ast.Subscript* = ast.Subscript*(node)
if sub.value is not None: return self._enum_annotation_name_is_ptr(sub.value)
if sub.value is not None and self._enum_annotation_name_is_ptr(sub.value) != 0: return 1
if sub.slice is not None: return self._enum_annotation_name_is_ptr(sub.slice)
return 0
return 0
def _enum_member_annotation_is_ptr(self, ann_node: ast.AST*) -> int:
"""Whether an enum member annotation (checking each union side) contains a pointer type, 1=yes / 0=no"""
if ann_node is None: return 0
if ann_node.kind() == ast.ASTKind.BinOp:
bop: ast.BinOp* = ast.BinOp*(ann_node)
if bop.op != ast.OpKind.BitOr: return 0
if self._enum_member_annotation_is_ptr(bop.left) == 1: return 1
return self._enum_member_annotation_is_ptr(bop.right)
"""Whether an enum member annotation contains a pointer type, 1=yes / 0=no"""
return self._enum_annotation_name_is_ptr(ann_node)
def _extract_int_value(self, node: ast.AST*) -> int64:
"""Extract an integer value from an AST node, 0 on failure"""
+16 -11
View File
@@ -837,20 +837,25 @@ novirtual class ExprGenAttr(ExprGenBase.ExprGenBase):
if fe is None: return None
return self.Gen.StructGen.get_field_annot_class_name(fe)
# _base_class_from_cast_base - bare class name of a pointer-cast base `T*(x)` (parsed as `(T*)(x)`, whose
# union-with-t.CPtr type is i8*), extracted from the BinOp annotation; bare name matches TPC's convention.
# _base_class_from_cast_base - bare class name of a pointer-cast base `T*(x)` (parsed as CPtr[T]),
# extracted from the marker subscript; bare name matches TPC's convention.
def _base_class_from_cast_base(self, pool: memhub.MemBuddy*, trans: HT.Translator*, node: ast.AST*) -> str:
"""Return the bare class name of a T*(x) cast base, or None for other forms."""
if node is None or trans is None: return None
if node.kind() != ast.ASTKind.Call: return None
cl_cb: ast.Call* = ast.Call*(node)
if cl_cb.func is None or cl_cb.func.kind() != ast.ASTKind.BinOp: return None
bo_cb: ast.BinOp* = ast.BinOp*(cl_cb.func)
if bo_cb.op != ast.OpKind.BitOr: return None
# when one side of the union is the t.CPtr marker (the nested union of `T*` / `T**`), take the
# other side; otherwise (a hand-written `X | Y`) take the left side, matching parser _make_ptr_type
tgt_cb: ast.AST* = bo_cb.left
if self.Gen.TypeGenResolve._is_cptr_marker(bo_cb.left, trans._imported_modules) != 0: tgt_cb = bo_cb.right
tgt_cb: ast.AST* = node
# A cast call node: the pointer type is the callee.
if tgt_cb.kind() == ast.ASTKind.Call:
cl_cb: ast.Call* = ast.Call*(tgt_cb)
if cl_cb.func is None: return None
tgt_cb = cl_cb.func
# Unwrap every CPtr[...] layer; the innermost node is the cast base.
while tgt_cb is not None and tgt_cb.kind() == ast.ASTKind.Subscript:
sub_cb: ast.Subscript* = ast.Subscript*(tgt_cb)
if sub_cb.value is None or sub_cb.value.kind() != ast.ASTKind.Name: break
mk_nm: ast.Name* = ast.Name*(sub_cb.value)
if mk_nm.id is None: break
if string.strcmp(mk_nm.id, "CPtr") != 0 and string.strcmp(mk_nm.id, "rawptr") != 0: break
tgt_cb = sub_cb.slice
if tgt_cb is None: return None
k_cb: int = tgt_cb.kind()
if k_cb == ast.ASTKind.Attribute:
+10
View File
@@ -344,6 +344,11 @@ novirtual class ExprGenCls(ExprGenBase.ExprGenBase):
if seglen > 0 and seglen < 256:
string.strncpy(seg, annot + s, seglen)
seg[seglen] = '\0'
# Unwrap the CPtr[T] pointer marker: the class name is inside.
while seglen > 6 and string.strncmp(seg, "CPtr[", 5) == 0 and seg[seglen - 1] == ']':
seg = seg + 5
seglen -= 6
seg[seglen] = '\0'
if self.Gen.StructGen.find_struct(seg) is not None: return seg
# On a find_struct miss, retry with the last '.'-prefix (module
# qualifier) stripped: memhub.MemBuddy -> MemBuddy.
@@ -363,6 +368,11 @@ novirtual class ExprGenCls(ExprGenBase.ExprGenBase):
if seglen > 0 and seglen < 256:
string.strncpy(seg, annot + s, seglen)
seg[seglen] = '\0'
# Unwrap the CPtr[T] pointer marker: the class name is inside.
while seglen > 6 and string.strncmp(seg, "CPtr[", 5) == 0 and seg[seglen - 1] == ']':
seg = seg + 5
seglen -= 6
seg[seglen] = '\0'
if self.Gen.StructGen.find_struct(seg) is not None: return seg
# On a find_struct miss, retry with the last '.'-prefix (module
# qualifier) stripped: memhub.MemBuddy -> MemBuddy.
+4 -2
View File
@@ -368,9 +368,11 @@ novirtual class ExprGenName(ExprGenBase.ExprGenBase):
if elem_buf_e is None: return None
string.strncpy(elem_buf_e, lb_e + 1, elen_e)
elem_buf_e[elen_e] = '\0'
# Strip the rawptr[...] wrapper (elements declared as object pointers, e.g. rawptr[sha1.AST])
# Strip the pointer wrapper (elements declared as object pointers): CPtr[...] / rawptr[...]
cur_e: str = elem_buf_e
if string.strncmp(cur_e, "rawptr[", 7) == 0: cur_e = cur_e + 7
while string.strncmp(cur_e, "CPtr[", 5) == 0 or string.strncmp(cur_e, "rawptr[", 7) == 0:
if string.strncmp(cur_e, "CPtr[", 5) == 0: cur_e = cur_e + 5
else: cur_e = cur_e + 7
# Split unions by '|', trimming leading/trailing whitespace, trailing ']' and quotes; each
# segment goes to find_struct (builtin names return None; "sha1.Class" prefix matches by module).
total_e: sizet = string.strlen(cur_e)
+11 -16
View File
@@ -425,19 +425,17 @@ novirtual class TranslatorGenMixin:
viperlib.snprintf(buf, body_len + 8, "{ %s }", body)
return buf
# Module-level global t.attr.* extraction: attributes live in the annotation union
# (`T | t.attr.section(".x")`) and are written directly to gv.Section/Align/Linkage fields.
# Module-level global attr.* extraction: attributes are marker wrappers
# (`attr.section(".x")[T]`) written directly to gv.Section/Align/Linkage fields.
def _scan_global_tattr(self, node: ast.AST*, want_name: str) -> ast.AST*:
"""Find the t.attr.<want_name> node in a global type annotation; None when absent."""
"""Find the attr.<want_name> marker in a global type annotation; None when absent."""
if node is None: return None
if node.kind() == ast.ASTKind.BinOp:
bop: ast.BinOp* = ast.BinOp*(node)
if bop.op == ast.OpKind.BitOr:
hit_l: ast.AST* = self._scan_global_tattr(bop.left, want_name)
if hit_l is not None: return hit_l
return self._scan_global_tattr(bop.right, want_name)
return None
if self.Gen.ClassGenBase._tattr_name(node, want_name) != 0: return node
if node.kind() == ast.ASTKind.Subscript:
sub_t: ast.Subscript* = ast.Subscript*(node)
hit_s: ast.AST* = self._scan_global_tattr(sub_t.value, want_name)
if hit_s is not None: return hit_s
return self._scan_global_tattr(sub_t.slice, want_name)
return None
def _extract_global_attr_section(self, node: ast.AST*, var_name: str) -> str:
"""Extract the section name from `T | t.attr.section("xxx")`; fail-fast when empty."""
@@ -960,12 +958,9 @@ novirtual class TranslatorGenMixin:
if cls_nm_ml is not None: self.Gen.VarGen.set_var_annot_class_name(trans.SymTab, var_name, cls_nm_ml)
# Module-level str*/bytes* globals: mark IsPtrElement so var[i] steps by 8-byte
# pointers; otherwise it degrades to a 1-byte GEP + load i8 that truncates pointers.
if aa_ml.annotation.kind() == ast.ASTKind.BinOp:
bop_ml: ast.BinOp* = ast.BinOp*(aa_ml.annotation)
if bop_ml.op == ast.OpKind.BitOr:
if self.Gen.AssignGen._annotation_has_cptr_marker(aa_ml.annotation) != 0:
if self.Gen.AssignGen._annotation_contains_name(aa_ml.annotation, "str") != 0 or self.Gen.AssignGen._annotation_contains_name(aa_ml.annotation, "bytes") != 0:
self.Gen.VarGen.set_var_ptr_element(trans.SymTab, var_name)
if self.Gen.AssignGen._annotation_has_cptr_marker(aa_ml.annotation) != 0:
if self.Gen.AssignGen._annotation_contains_name(aa_ml.annotation, "str") != 0 or self.Gen.AssignGen._annotation_contains_name(aa_ml.annotation, "bytes") != 0:
self.Gen.VarGen.set_var_ptr_element(trans.SymTab, var_name)
return 1
return 0
+12 -7
View File
@@ -140,17 +140,21 @@ novirtual class TypeGenErr(TypeGenBase.TypeGenBase):
k: int = node.kind()
# Name node: builtin markers CExtern / State / CExport / CInline / CStatic / CConst / CVolatile
# Name node: a bare builtin marker (CExtern / State / CExport / CInline / CStatic / CConst ...)
if k == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(node)
if nm.id is not None and string.strcmp(nm.id, marker_name) == 0: return 1
return 0
# BinOp node: int | CExtern (recurse into both children)
if k == ast.ASTKind.BinOp:
bop: ast.BinOp* = ast.BinOp*(node)
if self.has_decorator_marker(bop.left, marker_name, depth + 1) != 0: return 1
if self.has_decorator_marker(bop.right, marker_name, depth + 1) != 0: return 1
# Subscript node: a marker wrapper `CConst[T]` / descriptor chain; the marker may be on this
# level or nested inside the wrapped type.
if k == ast.ASTKind.Subscript:
sub_m: ast.Subscript* = ast.Subscript*(node)
if sub_m.value is not None and sub_m.value.kind() == ast.ASTKind.Name:
mv: ast.Name* = ast.Name*(sub_m.value)
if mv.id is not None and string.strcmp(mv.id, marker_name) == 0: return 1
if sub_m.slice is not None:
return self.has_decorator_marker(sub_m.slice, marker_name, depth + 1)
return 0
return 0
@@ -161,7 +165,8 @@ novirtual class TypeGenErr(TypeGenBase.TypeGenBase):
sub: ast.Subscript* = ast.Subscript*(node)
if sub.value is None or sub.value.kind() != ast.ASTKind.Name: return 0
rp_nm: ast.Name* = ast.Name*(sub.value)
if rp_nm.id is None or string.strcmp(rp_nm.id, "rawptr") != 0: return 0
if rp_nm.id is None: return 0
if string.strcmp(rp_nm.id, "rawptr") != 0 and string.strcmp(rp_nm.id, "CPtr") != 0: return 0
if sub.slice is None: return 0
return self.has_decorator_marker(sub.slice, "CVolatile", 0)
+118 -311
View File
@@ -77,21 +77,8 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase):
string.strcat(buf_s, "]")
return buf_s
return None
# BinOp (BitOr): build "left|right"
# BinOp (BitOr): retired `T1 | T2` union, never a type-name segment.
if k == ast.ASTKind.BinOp:
bop: ast.BinOp* = ast.BinOp*(node)
if bop.op == ast.OpKind.BitOr:
left_str: str = self._get_type_name_from_annotation(pool, ast.binop_get_left(bop))
right_str: str = self._get_type_name_from_annotation(pool, ast.binop_get_right(bop))
if left_str is not None and right_str is not None:
total2: sizet = string.strlen(left_str) + 1 + string.strlen(right_str) + 1
buf2: str = pool.alloc(total2)
if buf2 is not None:
buf2[0] = '\0'
string.strcat(buf2, left_str)
string.strcat(buf2, "|")
string.strcat(buf2, right_str)
return buf2
return None
return None
def _subscript_slice_to_str(self, pool: memhub.MemBuddy*, node: ast.AST*) -> str:
@@ -320,51 +307,10 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase):
# whitespace is dirty data as a type name / table key, so skip it before normalizing.
while type_name[0] == ' ': type_name += 1
if type_name[0] == '\0': return type_name
# ---- 1. top-level union "A|B" ('|' splits only outside brackets) ----
# Bracket-aware: a '|' inside list[A|B] belongs to the generic-slice step, not here.
# ---- 1. retired union text "A|B": no longer a valid type ----
seg_len: sizet = string.strlen(type_name)
if seg_len == 0: return type_name
pipe_idx: sizet = seg_len
p_depth: int = 0
for pi in range(int(seg_len)):
ch_p: int = type_name[pi]
if ch_p == '[': p_depth += 1
elif ch_p == ']': p_depth -= 1
elif ch_p == '|' and p_depth == 0:
pipe_idx = pi
break
if pipe_idx < seg_len:
left_buf: str = pool.alloc(pipe_idx + 1)
if left_buf is None: return type_name
string.strncpy(left_buf, type_name, pipe_idx)
left_buf[pipe_idx] = '\0'
right_str: str = type_name + pipe_idx + 1
right_len: sizet = string.strlen(right_str)
right_buf: str = pool.alloc(right_len + 1)
if right_buf is None: return type_name
string.strcpy(right_buf, right_str)
l_n: str = self._norm_type_arg_name(pool, left_buf, from_imports)
r_n: str = self._norm_type_arg_name(pool, right_buf, from_imports)
if l_n is None: l_n = left_buf
if r_n is None: r_n = right_buf
# [pointer union normalization] `X*` == `X | rawptr` == `rawptr[X]` == `t.CPtr[X]`
# -> fold to `X*`; only intercepts the case where one side is exactly a bare marker.
l_is_mk: int = self._is_ptr_marker_text(l_n)
r_is_mk: int = self._is_ptr_marker_text(r_n)
if l_is_mk == 0 and r_is_mk != 0:
_star_r: str = self._ptr_star_text(pool, l_n)
if _star_r is not None: return _star_r
if l_is_mk != 0 and r_is_mk == 0:
_star_l: str = self._ptr_star_text(pool, r_n)
if _star_l is not None: return _star_l
total: sizet = string.strlen(l_n) + 1 + string.strlen(r_n) + 1
out: str = pool.alloc(total)
if out is None: return type_name
out[0] = '\0'
string.strcat(out, l_n)
string.strcat(out, "|")
string.strcat(out, r_n)
return out
if string.strchr(type_name, '|') is not None: return None
# ---- 1.5 nested generic args "Name[A,B]...": split inner args on top-level ',',
# normalize each; t.CArray / list keep their count arg verbatim.
lb_idx: sizet = seg_len
@@ -648,26 +594,8 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase):
_wrap_n = ast.Subscript(pool, _raw_nm, _wrap_n, ast.ASTCtx.Load)
return _wrap_n
# Check for '|' (BinOp): search only within the valid range [0, seg_len)
pipe_idx: sizet = seg_len
for pi in range(int(seg_len)):
if type_str[pi] == '|':
pipe_idx = pi
break
if pipe_idx < seg_len:
# Copy the left segment into its own buffer (trim trailing spaces), preserving the original
left_len: sizet = pipe_idx
while left_len > 0 and type_str[left_len - 1] == ' ': left_len -= 1
left_buf: bytes = stdlib.malloc(left_len + 1)
if left_buf is None: return None
string.strncpy(left_buf, type_str, left_len)
left_buf[left_len] = '\0'
left_node: ast.AST* = self._parse_type_string(pool, left_buf, ctx)
# Leading spaces of the right segment are skipped by the recursive entry
right_node: ast.AST* = self._parse_type_string(pool, type_str + pipe_idx + 1, ctx)
stdlib.free(left_buf)
if left_node is not None and right_node is not None: return ast.BinOp(pool, left_node, ast.OpKind.BitOr, right_node)
return None
# `T1 | T2` unions were retired: an annotation string containing '|' is invalid.
if string.strchr(type_str, '|') is not None: return None
# Check for '[' (Subscript) like t.Callable[[int], int]; must precede the '.'
# check since it is a Subscript. Parse fully via ast.parse_expression.
@@ -959,20 +887,22 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase):
out_buf[ci] = buf[line_start + name_s + ci]
ci += 1
out_buf[name_l] = '\0'
# Normalize a C-style typedef RHS (`const T` / `T*`) into the canonical `|` union text,
# mirroring TPC's TypeAnnotationResolver._NormalizeTypedefRhsText: leading const/volatile
# become `CConst |` / `CVolatile |`, trailing `*` runs become ` | CPtr`. Text without
# these forms is copied verbatim (byte-identical to the pre-normalization behavior).
# Normalize a C-style typedef RHS (`const T` / `T*`) into marker-wrapper text:
# leading const/volatile become `CConst[` / `CVolatile[`, trailing `*` runs become `CPtr[`,
# and the matching closing brackets are appended after the type text.
pos: sizet = name_l + 1
k: sizet = vs
descr: sizet = 0
while True:
if k + 6 <= ve and string.strncmp(buf + line_start + k, "const", 5) == 0 and (buf[line_start + k + 5] == ' ' or buf[line_start + k + 5] == '\t'):
string.memcpy(out_buf + pos, "CConst | ", 9)
pos += 9
string.memcpy(out_buf + pos, "CConst[", 7)
pos += 7
descr += 1
k += 6
elif k + 9 <= ve and string.strncmp(buf + line_start + k, "volatile", 8) == 0 and (buf[line_start + k + 8] == ' ' or buf[line_start + k + 8] == '\t'):
string.memcpy(out_buf + pos, "CVolatile | ", 12)
pos += 12
string.memcpy(out_buf + pos, "CVolatile[", 10)
pos += 10
descr += 1
k += 9
else:
break
@@ -983,19 +913,26 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase):
ptrs += 1
ve2 -= 1
while ve2 > k and (buf[line_start + ve2 - 1] == ' ' or buf[line_start + ve2 - 1] == '\t'): ve2 -= 1
# Pointer prefixes wrap the value: CPtr[ repeatedly.
for pi in range(int(ptrs)):
string.memcpy(out_buf + pos, "CPtr[", 5)
pos += 5
vlen2: sizet = ve2 - k
if vlen2 > 190: vlen2 = 190
# Output capacity guard (register_ctypedef_from_source allocates 512 bytes for NAME\0VALUE\0).
if pos + vlen2 + ptrs * 7 > 500:
if pos + ptrs * 7 >= 500: ptrs = 0
if pos + ptrs * 7 >= 500: vlen2 = 0
else: vlen2 = 500 - pos - ptrs * 7
closers: sizet = ptrs + descr
if pos + vlen2 + closers > 500:
if pos + closers >= 500:
# Prefixes are already written; keep them balanced by emitting only closers.
vlen2 = 0
else:
vlen2 = 500 - pos - closers
for ci2 in range(int(vlen2)):
out_buf[pos + ci2] = buf[line_start + k + ci2]
pos += vlen2
for pi in range(int(ptrs)):
string.memcpy(out_buf + pos, " | CPtr", 7)
pos += 7
for ci3 in range(int(closers)):
out_buf[pos] = ']'
pos += 1
out_buf[pos] = '\0'
return 1
def _lookup_builtins_typedef(self, pool: memhub.MemBuddy*, name: str) -> str:
@@ -1508,23 +1445,17 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase):
def _ref_marker_name_mode(self, node: ast.AST*) -> int:
"""Syntactic value-reference marker test (no import context): 0=none / 1=CRef / 2=CTempRef."""
if node is None: return 0
nm: str = None
if node.kind() == ast.ASTKind.Name:
nm = (ast.Name*(node)).id
if node.kind() != ast.ASTKind.Subscript: return 0
sub_r: ast.Subscript* = ast.Subscript*(node)
if sub_r.value is None or sub_r.value.kind() != ast.ASTKind.Name: return 0
nm: str = (ast.Name*(sub_r.value)).id
if nm is None: return 0
if string.strcmp(nm, "CRef") == 0: return 1
if string.strcmp(nm, "CTempRef") == 0: return 2
return 0
def ref_param_mode(self, node: ast.AST*, imported_modules: str) -> int:
"""Value-reference mode of a top-level union annotation: 0=none / 1=ref (&T) / 2=temp (&temp T)."""
if node is None or node.kind() != ast.ASTKind.BinOp: return 0
bo: ast.BinOp* = ast.BinOp*(node)
if bo.op != ast.OpKind.BitOr: return 0
if self._is_builtin_marker(bo.left, "CTempRef", imported_modules) != 0: return 2
if self._is_builtin_marker(bo.right, "CTempRef", imported_modules) != 0: return 2
if self._is_builtin_marker(bo.left, "CRef", imported_modules) != 0: return 1
if self._is_builtin_marker(bo.right, "CRef", imported_modules) != 0: return 1
return 0
"""Value-reference mode of a parameter annotation: 0=none / 1=ref (&T) / 2=temp (&temp T)."""
return self._ref_marker_name_mode(node)
def reject_ref_marker_position(self, node: ast.AST*, imported_modules: str, where: str) -> int:
"""Fail fast when a value-reference marker (&T / &temp T) is used outside a parameter position."""
if self.ref_param_mode(node, imported_modules) == 0: return 0
@@ -1554,12 +1485,24 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase):
if rn.id is None or string.strcmp(rn.id, "attr") != 0: return 0
return 1
def _is_cvoid_marker(self, node: ast.AST*, imported_modules: str) -> int:
"""Whether a node is a CVoid pointer marker: 1=yes / 0=no."""
"""Whether a node is a CVoid / void marker: 1=yes / 0=no."""
if self._is_builtin_marker(node, "CVoid", imported_modules) != 0: return 1
if node is not None and node.kind() == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(node)
# Bare CVoid is synonymous with the builtin alias void
if nm.id is not None and string.strcmp(nm.id, "void") == 0: return 1
# Storage / descriptor wrappers keep the wrapped type's void-ness (a pointer wrapper does not).
if node is not None and node.kind() == ast.ASTKind.Subscript:
sub_v: ast.Subscript* = ast.Subscript*(node)
if sub_v.value is not None and sub_v.value.kind() == ast.ASTKind.Name:
sw_nm: ast.Name* = ast.Name*(sub_v.value)
if sw_nm.id is not None and (string.strcmp(sw_nm.id, "CConst") == 0 or string.strcmp(sw_nm.id, "CVolatile") == 0 \
or string.strcmp(sw_nm.id, "BigEndian") == 0 or string.strcmp(sw_nm.id, "LittleEndian") == 0 \
or string.strcmp(sw_nm.id, "CStatic") == 0 or string.strcmp(sw_nm.id, "CExport") == 0 \
or string.strcmp(sw_nm.id, "CExtern") == 0 or string.strcmp(sw_nm.id, "State") == 0 \
or string.strcmp(sw_nm.id, "CDefine") == 0 or string.strcmp(sw_nm.id, "CRef") == 0 \
or string.strcmp(sw_nm.id, "CTempRef") == 0 or string.strcmp(sw_nm.id, "CAutoPtr") == 0):
if sub_v.slice is not None: return self._is_cvoid_marker(sub_v.slice, imported_modules)
return 0
def annotation_to_str(self, annot: ast.AST*) -> str:
"""Render a type-annotation AST to a readable string; None means not extractable."""
@@ -1591,9 +1534,36 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase):
return buf
return at.attr
# Subscript node: list[...], t.CArray[...]
# Subscript node: list[...], CArray[...], CPtr[...] (rendered as T*), marker wrappers.
if k == ast.ASTKind.Subscript:
sub: ast.Subscript* = ast.Subscript*(annot)
if self._is_cattr_marker(sub.value) != 0: return self.annotation_to_str(sub.slice)
if sub.value.kind() == ast.ASTKind.Name:
sv_nm: ast.Name* = ast.Name*(sub.value)
if sv_nm.id is not None:
if string.strcmp(sv_nm.id, "CPtr") == 0:
inner_cp: str = self.annotation_to_str(sub.slice)
if inner_cp is not None:
cp_buf: str = stdlib.malloc(string.strlen(inner_cp) + 2)
if cp_buf is not None:
viperlib.snprintf(cp_buf, string.strlen(inner_cp) + 2, "%s*", inner_cp)
return cp_buf
return "<subscript>"
if string.strcmp(sv_nm.id, "CRef") == 0 or string.strcmp(sv_nm.id, "CTempRef") == 0:
inner_rf: str = self.annotation_to_str(sub.slice)
if inner_rf is not None:
rf_prefix: str = "&temp " if string.strcmp(sv_nm.id, "CTempRef") == 0 else "&"
rf_buf: str = stdlib.malloc(string.strlen(inner_rf) + string.strlen(rf_prefix) + 1)
if rf_buf is not None:
viperlib.snprintf(rf_buf, string.strlen(inner_rf) + string.strlen(rf_prefix) + 1, "%s%s", rf_prefix, inner_rf)
return rf_buf
return "<subscript>"
if string.strcmp(sv_nm.id, "CConst") == 0 or string.strcmp(sv_nm.id, "CVolatile") == 0 \
or string.strcmp(sv_nm.id, "BigEndian") == 0 or string.strcmp(sv_nm.id, "LittleEndian") == 0 \
or string.strcmp(sv_nm.id, "CStatic") == 0 or string.strcmp(sv_nm.id, "CExport") == 0 \
or string.strcmp(sv_nm.id, "CExtern") == 0 or string.strcmp(sv_nm.id, "State") == 0 \
or string.strcmp(sv_nm.id, "CDefine") == 0 or string.strcmp(sv_nm.id, "CAutoPtr") == 0:
return self.annotation_to_str(sub.slice)
base: str = self.annotation_to_str(sub.value)
if base is not None:
buf2: str = stdlib.malloc(string.strlen(base) + 5)
@@ -1602,40 +1572,6 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase):
return buf2
return "<subscript>"
# BinOp node: A | B (union type); value-reference params render as `&T` / `&temp T`
if k == ast.ASTKind.BinOp:
bo: ast.BinOp* = ast.BinOp*(annot)
if bo.op == ast.OpKind.BitOr:
m_left: int = self._ref_marker_name_mode(bo.left)
m_right: int = self._ref_marker_name_mode(bo.right)
if m_left != 0 or m_right != 0:
other_rs: ast.AST* = bo.left
m_rs: int = m_left
if m_left != 0:
other_rs = bo.right
else:
m_rs = m_right
inner_rs: str = self.annotation_to_str(other_rs)
if inner_rs is not None:
if m_rs == 2:
buf_tr: str = stdlib.malloc(string.strlen(inner_rs) + 7)
if buf_tr is not None:
viperlib.snprintf(buf_tr, string.strlen(inner_rs) + 7, "&temp %s", inner_rs)
return buf_tr
else:
buf_rr: str = stdlib.malloc(string.strlen(inner_rs) + 2)
if buf_rr is not None:
viperlib.snprintf(buf_rr, string.strlen(inner_rs) + 2, "&%s", inner_rs)
return buf_rr
left_s: str = self.annotation_to_str(bo.left)
right_s: str = self.annotation_to_str(bo.right)
if left_s is not None and right_s is not None:
buf3: str = stdlib.malloc(string.strlen(left_s) + string.strlen(right_s) + 5)
if buf3 is not None:
viperlib.snprintf(buf3, string.strlen(left_s) + string.strlen(right_s) + 5, "%s | %s", left_s, right_s)
return buf3
return "<binop>"
return "<unknown>"
def _is_storage_class_qualifier(self, name: str) -> int:
@@ -1715,38 +1651,34 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase):
if alias_mod2 is not None: stdlib.free(alias_mod2)
return at.attr
# BinOp node: union type A | B
if k == ast.ASTKind.BinOp:
bop: ast.BinOp* = ast.BinOp*(node)
if bop.op == ast.OpKind.BitOr:
left_is_cptr: int = self._is_cptr_marker(bop.left, imported_modules)
right_is_cptr: int = self._is_cptr_marker(bop.right, imported_modules)
# Extract the class name from the non-CPtr side
if left_is_cptr != 0 and right_is_cptr == 0: return self.extract_class_name_from_annotation(bop.right, imported_modules, pool, from_imports)
if right_is_cptr != 0 and left_is_cptr == 0: return self.extract_class_name_from_annotation(bop.left, imported_modules, pool, from_imports)
# No CPtr marker: try both sides but prefer the real type, since storage-class
# qualifiers (CStatic/CVolatile/...) would otherwise be recorded as the class.
if left_is_cptr == 0 and right_is_cptr == 0:
left_name: str = self.extract_class_name_from_annotation(bop.left, imported_modules, pool, from_imports)
right_name: str = self.extract_class_name_from_annotation(bop.right, imported_modules, pool, from_imports)
if left_name is not None and self._is_storage_class_qualifier(left_name) == 0: return left_name
if right_name is not None and self._is_storage_class_qualifier(right_name) == 0: return right_name
if left_name is not None: return left_name
return right_name
return None
# BinOp nodes are never types (unions retired).
if k == ast.ASTKind.BinOp: return None
# Subscript node: build the mangled name (list[AST*] -> "list[ast.AST*]" when pool is
# available) matching resolve_annotation_type so find_struct finds the specialization.
if k == ast.ASTKind.Subscript:
sub: ast.Subscript* = ast.Subscript*(node)
if sub.value is None: return None
# rawptr[T] is a builtin bare pointer (equivalent to t.CPtr[T]); AnnotClassName
# should take the pointee class T, else lookup_field_by_class reports 'rawptr' unregistered.
# `attr.<name>[T]` marker wrapper: the class name lives in the wrapped type.
if self._is_cattr_marker(sub.value) != 0:
if sub.slice is not None: return self.extract_class_name_from_annotation(sub.slice, imported_modules, pool, from_imports)
return None
if sub.value.kind() == ast.ASTKind.Name:
rp_nm: ast.Name* = ast.Name*(sub.value)
if rp_nm.id is not None and string.strcmp(rp_nm.id, "rawptr") == 0:
if sub.slice is not None: return self.extract_class_name_from_annotation(sub.slice, imported_modules, pool, from_imports)
return None
if rp_nm.id is not None:
# rawptr[T] / CPtr[T]: AnnotClassName takes the pointee class T.
if string.strcmp(rp_nm.id, "rawptr") == 0 or string.strcmp(rp_nm.id, "CPtr") == 0:
if sub.slice is not None: return self.extract_class_name_from_annotation(sub.slice, imported_modules, pool, from_imports)
return None
# Descriptor / storage / value-reference marker wrappers: unwrap to the type.
if string.strcmp(rp_nm.id, "CConst") == 0 or string.strcmp(rp_nm.id, "CVolatile") == 0 \
or string.strcmp(rp_nm.id, "BigEndian") == 0 or string.strcmp(rp_nm.id, "LittleEndian") == 0 \
or string.strcmp(rp_nm.id, "CStatic") == 0 or string.strcmp(rp_nm.id, "CExport") == 0 \
or string.strcmp(rp_nm.id, "CExtern") == 0 or string.strcmp(rp_nm.id, "State") == 0 \
or string.strcmp(rp_nm.id, "CDefine") == 0 or string.strcmp(rp_nm.id, "CRef") == 0 \
or string.strcmp(rp_nm.id, "CTempRef") == 0 or string.strcmp(rp_nm.id, "CAutoPtr") == 0:
if sub.slice is not None: return self.extract_class_name_from_annotation(sub.slice, imported_modules, pool, from_imports)
return None
# When recursing into base, from_imports must be passed down too, else the mangled
# name differs across dispatch/table/register keys and crashes from splitting.
base_name: str = self.extract_class_name_from_annotation(sub.value, imported_modules, pool, from_imports)
@@ -2333,156 +2265,14 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase):
if td_ty_na is not None: return td_ty_na
return None
# BinOp node: union annotation A | B. Three semantics:
# 1. ActualType* -> Ptr(ActualType); 2. t.CPtr -> i8*; 3. ActualType | DecoratorMarker -> ActualType.
# BinOp nodes never represent types: `T1 | T2` unions were retired. Every former union form
# has dedicated syntax (T*, const/volatile/bigend/littlend, &T/&temp T, export/extern/static).
if k == ast.ASTKind.BinOp:
bop: ast.BinOp* = ast.BinOp*(node)
if bop.op == ast.OpKind.BitOr:
# Byte-order markers: annotation `|` means "and", so `ActualType | t.BigEndian`
# yields the actual type; the byte order itself is recorded on the field entry
# (see ClassGenDef field_byteorder_buf), not carried in the LLVM type.
left_bo: int = self._is_byteorder_marker(bop.left, imported_modules)
right_bo: int = self._is_byteorder_marker(bop.right, imported_modules)
if left_bo != 0 or right_bo != 0:
other_node_bo: ast.AST* = bop.right
if right_bo != 0: other_node_bo = bop.left
if other_node_bo is not None:
other_ty_bo: mlirlite.LLVMType* = self.resolve_annotation_type(pool, other_node_bo, imported_modules, from_imports, trans, depth + 1)
if other_ty_bo is not None: return other_ty_bo
# Markers on both sides (e.g. `t.BigEndian | t.LittleEndian`) -> CInt.
return mlirlite.Int32(pool)
# Detect the t.CPtr marker (supports t.CPtr and the bare from t import CPtr name).
# CRef/CTempRef (value-reference parameter markers) resolve identically here: the
# parameter ABI is Ptr(other); the ref/temp distinction only drives call-site auto-address.
left_is_cptr: int = self._is_cptr_marker(bop.left, imported_modules)
right_is_cptr: int = self._is_cptr_marker(bop.right, imported_modules)
left_is_ref: int = self._is_ref_marker(bop.left, imported_modules)
right_is_ref: int = self._is_ref_marker(bop.right, imported_modules)
if left_is_cptr != 0 or right_is_cptr != 0 or left_is_ref != 0 or right_is_ref != 0:
# CPtr/ref present: resolve the actual type on the other side and wrap it in Ptr
# (left marker -> other is right; right marker -> other is left).
other_node: ast.AST* = bop.left
if left_is_cptr != 0 or left_is_ref != 0: other_node = bop.right
# Detect whether the other side is CVoid / void (void* = i8*).
other_is_cvoid: int = self._is_cvoid_marker(other_node, imported_modules)
if other_is_cvoid != 0: return mlirlite.Ptr(pool, mlirlite.Int8(pool))
other_ty: mlirlite.LLVMType* = self.resolve_annotation_type(pool, other_node, imported_modules, from_imports, trans, depth + 1)
# If the result is void, merging with CPtr gives void* = i8*; must guard None
# (an unresolved `X*` in Phase 1a prescan would otherwise deref null).
if other_ty is not None:
match other_ty:
case mlirlite.LLVMType.Void():
return mlirlite.Ptr(pool, mlirlite.Int8(pool))
case _:
pass
# Only bare names require the resolved type to be a registered struct; Attribute
# results (uint64, memhub.MemBuddy) are usable and must not be kicked to fallback.
other_is_name: int = 0
if other_node is not None and other_node.kind() == ast.ASTKind.Name: other_is_name = 1
if other_ty is not None and other_is_name != 0:
if self.Gen.StructGen.find_struct_by_type(other_ty) is None:
# A generic param (T->int) resolved to a valid scalar must be kept, else
# T* (T=int) degrades to i8* and corrupts list[int] element access.
keep_other_ty: int = 0
tpn_g2: list[str]* = self.Gen.TypeGenBase.GenericTpNames
if tpn_g2 is not None:
other_nm_x: ast.Name* = ast.Name*(other_node)
gtn_nx: sizet = len(tpn_g2)
gtn_ix: sizet
for gtn_ix in range(int(gtn_nx)):
gtn_nmx: str = tpn_g2.get(gtn_ix)
if gtn_nmx is not None and other_nm_x.id is not None:
if string.strcmp(gtn_nmx, other_nm_x.id) == 0:
keep_other_ty = 1
break
# Builtin scalar names (int/str/bytes/bool/float/double) are valid scalars,
# not garbage; dropping them would degrade int* to i8* and truncate writes.
if keep_other_ty == 0 and other_node is not None and other_node.kind() == ast.ASTKind.Name:
other_nm_b: ast.Name* = ast.Name*(other_node)
if other_nm_b.id is not None:
if string.strcmp(other_nm_b.id, "int") == 0 or string.strcmp(other_nm_b.id, "char") == 0 or string.strcmp(other_nm_b.id, "str") == 0 or string.strcmp(other_nm_b.id, "bytes") == 0 or string.strcmp(other_nm_b.id, "bool") == 0 or string.strcmp(other_nm_b.id, "float") == 0 or string.strcmp(other_nm_b.id, "double") == 0:
keep_other_ty = 1
# Preserve typedef aliases: dropping them degrades the union to i8*; use
# resolve_typedef_or_map to decide. `| t.CPtr` always appends exactly 1 pointer layer.
if keep_other_ty == 0 and other_node is not None and other_node.kind() == ast.ASTKind.Name:
other_nm_td: ast.Name* = ast.Name*(other_node)
if other_nm_td.id is not None:
td_res_is_ptr: int = 0
match other_ty:
case mlirlite.LLVMType.Ptr(pointee_td):
if self.Gen.StructGen.find_struct_by_type(pointee_td) is not None: td_res_is_ptr = 1
else: td_res_is_ptr = 0
case _:
td_res_is_ptr = 0
if td_res_is_ptr == 0:
td_alias_ty: mlirlite.LLVMType* = self.resolve_typedef_or_map(pool, other_nm_td.id, imported_modules, from_imports, trans)
if td_alias_ty is not None: keep_other_ty = 1
if keep_other_ty == 0: other_ty = None
if other_ty is None:
# In strict mode, fall back to the global struct registry by class name:
# `X*` with X registered must not silently degrade to i8*. Bare names only.
other_cls_nm: str = None
if other_node is not None and other_node.kind() == ast.ASTKind.Name:
other_nm: ast.Name* = ast.Name*(other_node)
if other_nm.id is not None: other_cls_nm = other_nm.id
if other_cls_nm is not None:
other_cls_ty: mlirlite.LLVMType* = self.Gen.StructGen.get_struct_type(other_cls_nm)
if other_cls_ty is not None: return mlirlite.Ptr(pool, other_cls_ty)
# Unregistered class: forward self-reference, Phase A-pre deferral, or circular
# dependency. Prefer a forward declaration; only fall back to Ptr(i8) as a last resort.
_fwd_ptr_ty: mlirlite.LLVMType* = self._forward_declare_ptr_from_node(pool, other_node, imported_modules, from_imports)
if _fwd_ptr_ty is not None: return _fwd_ptr_ty
return mlirlite.Ptr(pool, mlirlite.Int8(pool))
# `X*` always appends exactly 1 pointer layer (same as rawptr[T]/t.CPtr[T]/`T*`).
# The old "absorb a layer for a registered-struct pointee" compensation is removed.
return mlirlite.Ptr(pool, other_ty)
# t.CPtr[...] subscript pointer marker (plain _is_cptr_marker does not recognize
# Subscript): merge pointer levels (CPtr side contributes PtrCount), e.g. u8*[t.CPtr] -> u8**.
cptr_sub_side: ast.AST* = None
cptr_other: ast.AST* = None
if self._is_cptr_subscript(bop.left) != 0:
cptr_sub_side = bop.left
cptr_other = bop.right
elif self._is_cptr_subscript(bop.right) != 0:
cptr_sub_side = bop.right
cptr_other = bop.left
if cptr_sub_side is not None and cptr_other is not None:
cptr_sub_ty: mlirlite.LLVMType* = self.resolve_annotation_type(pool, cptr_sub_side, imported_modules, from_imports, trans, depth + 1)
cptr_other_ty: mlirlite.LLVMType* = self.resolve_annotation_type(pool, cptr_other, imported_modules, from_imports, trans, depth + 1)
if cptr_sub_ty is not None and cptr_other_ty is not None:
# Count the outer Ptr nesting on the CPtr side (t.CPtr[t.CPtr]=i8** -> 2 layers)
cptr_level: int = 0
cur_lvl: mlirlite.LLVMType* = cptr_sub_ty
while cur_lvl is not None:
# Do not update the while condition variable inside a case body; bind
# pointee to a local and update outside the match instead.
nxt_lvl: mlirlite.LLVMType* = None
match cur_lvl:
case mlirlite.LLVMType.Ptr(pointee):
cptr_level += 1
nxt_lvl = pointee
case _:
break
cur_lvl = nxt_lvl
# Result = wrap the other side's type in N layers of Ptr
res_ty: mlirlite.LLVMType* = cptr_other_ty
for li2 in range(int(cptr_level)):
res_ty = mlirlite.Ptr(pool, res_ty)
return res_ty
# No CPtr marker: return the first non-attribute-marker side. Attribute markers
# (t.attr.<name>) are not types and are prepended left, so skip them.
if self._is_cattr_marker(bop.left) == 0:
left_ty: mlirlite.LLVMType* = self.resolve_annotation_type(pool, bop.left, imported_modules, from_imports, trans, depth + 1)
if left_ty is not None: return left_ty
if self._is_cattr_marker(bop.right) == 0:
right_ty: mlirlite.LLVMType* = self.resolve_annotation_type(pool, bop.right, imported_modules, from_imports, trans, depth + 1)
if right_ty is not None: return right_ty
self.Gen.TypeGenErr.fatal_type_error(node, "type union `T1 | T2` was removed; use `T*`, a prefix descriptor (const/volatile/bigend/littlend), `&T` / `&temp T`, or export/extern/static instead")
return None
# Subscript node: t.CArray[elem, count] -> [count x elem]; t.CArray[elem] -> Ptr(elem);
# list[int] -> specialized struct type. value is Attribute(Name('t'),'CArray') or Name('list').
# Subscript node: CArray[elem, count] -> [count x elem]; CArray[elem] -> Ptr(elem);
# list[int] -> specialized struct type; marker wrappers (CPtr[T], CConst[T], ...) -> unwrap.
if k == ast.ASTKind.Subscript:
sub: ast.Subscript* = ast.Subscript*(node)
if sub.value is None: self.Gen.TypeGenErr.fatal_type_error(node, "Subscript node missing value")
@@ -2496,6 +2286,19 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase):
if sub.value.kind() == ast.ASTKind.Name:
gen_nm: ast.Name* = ast.Name*(sub.value)
if gen_nm.id is not None:
# Builtin marker wrappers: descriptor / storage / value-reference markers carry
# no LLVM type of their own, so resolve the wrapped type.
if string.strcmp(gen_nm.id, "CConst") == 0 or string.strcmp(gen_nm.id, "CVolatile") == 0 \
or string.strcmp(gen_nm.id, "BigEndian") == 0 or string.strcmp(gen_nm.id, "LittleEndian") == 0 \
or string.strcmp(gen_nm.id, "CStatic") == 0 or string.strcmp(gen_nm.id, "CExport") == 0 \
or string.strcmp(gen_nm.id, "CExtern") == 0 or string.strcmp(gen_nm.id, "State") == 0 \
or string.strcmp(gen_nm.id, "CDefine") == 0 or string.strcmp(gen_nm.id, "CAutoPtr") == 0:
return self.resolve_annotation_type(pool, sub.slice, imported_modules, from_imports, trans, depth + 1)
# Value-reference parameters use the pointer ABI: &T / &temp T -> T*.
if string.strcmp(gen_nm.id, "CRef") == 0 or string.strcmp(gen_nm.id, "CTempRef") == 0:
ref_inner: mlirlite.LLVMType* = self.resolve_annotation_type(pool, sub.slice, imported_modules, from_imports, trans, depth + 1)
if ref_inner is None: return None
return mlirlite.Ptr(pool, ref_inner)
# Builtin fixed-array constructor array[E, N] (soft keyword, like t.CArray[E, N]):
# active only in type positions; only exactly two args with a positive length.
if string.strcmp(gen_nm.id, "array") == 0:
@@ -2572,6 +2375,10 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase):
fb_ty: mlirlite.LLVMType* = self.resolve_annotation_type(pool, fb_node, imported_modules, from_imports, trans, depth + 1)
if fb_ty is not None: return fb_ty
return None
# Builtin attribute marker wrapper (`attr.packed[T]` / `attr.section("x")[T]`):
# the marker carries no LLVM type, so resolve the wrapped type.
if self._is_cattr_marker(sub.value) != 0:
return self.resolve_annotation_type(pool, sub.slice, imported_modules, from_imports, trans, depth + 1)
# Check whether value is an Attribute (module-qualified generic like numpy.ndarray[T])
if sub.value.kind() != ast.ASTKind.Attribute: self.Gen.TypeGenErr.fatal_type_error(node, "Unsupported Subscript type annotation (only CArray / Callable are supported)")
arr_at: ast.Attribute* = ast.Attribute*(sub.value)