From 82a735c367b84ccd0961a5f1bf410ec823456727 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Sun, 18 Oct 2026 13:39:38 +0800 Subject: [PATCH] =?UTF-8?q?refactor(viperc):=20=E5=AE=8C=E5=85=A8=E5=88=A0?= =?UTF-8?q?=E9=99=A4=20T1=20|=20T2=20=E7=B1=BB=E5=9E=8B=E7=BB=84=E5=90=88?= =?UTF-8?q?=EF=BC=8C=E8=AF=AD=E6=B3=95=E7=B3=96=E6=94=B9=E7=94=A8=E6=A0=87?= =?UTF-8?q?=E8=AE=B0=E4=B8=8B=E6=A0=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- App/lib/Stub/StubGen/Converter.vp | 15 +- App/lib/Stub/StubGen/VpiReader.vp | 45 +++ App/lib/core/Gen/AssignGenMixin.vp | 167 +++++---- App/lib/core/Gen/Call/CallGenCtor.vp | 17 +- App/lib/core/Gen/Call/CallGenMixin.vp | 12 +- App/lib/core/Gen/Class/ClassGenEnum.vp | 44 ++- App/lib/core/Gen/Expr/ExprGenAttr.vp | 27 +- App/lib/core/Gen/Expr/ExprGenCls.vp | 10 + App/lib/core/Gen/Expr/ExprGenName.vp | 6 +- App/lib/core/Gen/TranslatorGenMixin.vp | 27 +- App/lib/core/Gen/Type/TypeGenErr.vp | 19 +- App/lib/core/Gen/Type/TypeGenResolve.vp | 429 +++++++----------------- 12 files changed, 355 insertions(+), 463 deletions(-) diff --git a/App/lib/Stub/StubGen/Converter.vp b/App/lib/Stub/StubGen/Converter.vp index 3eabcc6..8c60975 100644 --- a/App/lib/Stub/StubGen/Converter.vp +++ b/App/lib/Stub/StubGen/Converter.vp @@ -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) diff --git a/App/lib/Stub/StubGen/VpiReader.vp b/App/lib/Stub/StubGen/VpiReader.vp index 40b4bae..017f453 100644 --- a/App/lib/Stub/StubGen/VpiReader.vp +++ b/App/lib/Stub/StubGen/VpiReader.vp @@ -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.` / `attr.(...)` 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. diff --git a/App/lib/core/Gen/AssignGenMixin.vp b/App/lib/core/Gen/AssignGenMixin.vp index 6eff43f..454687d 100644 --- a/App/lib/core/Gen/AssignGenMixin.vp +++ b/App/lib/core/Gen/AssignGenMixin.vp @@ -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 ` | 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 diff --git a/App/lib/core/Gen/Call/CallGenCtor.vp b/App/lib/core/Gen/Call/CallGenCtor.vp index 4a471af..2f66481 100644 --- a/App/lib/core/Gen/Call/CallGenCtor.vp +++ b/App/lib/core/Gen/Call/CallGenCtor.vp @@ -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 diff --git a/App/lib/core/Gen/Call/CallGenMixin.vp b/App/lib/core/Gen/Call/CallGenMixin.vp index f05a9f0..66acf68 100644 --- a/App/lib/core/Gen/Call/CallGenMixin.vp +++ b/App/lib/core/Gen/Call/CallGenMixin.vp @@ -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. diff --git a/App/lib/core/Gen/Class/ClassGenEnum.vp b/App/lib/core/Gen/Class/ClassGenEnum.vp index 864c733..24e8f9f 100644 --- a/App/lib/core/Gen/Class/ClassGenEnum.vp +++ b/App/lib/core/Gen/Class/ClassGenEnum.vp @@ -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""" diff --git a/App/lib/core/Gen/Expr/ExprGenAttr.vp b/App/lib/core/Gen/Expr/ExprGenAttr.vp index 3a10d87..2057763 100644 --- a/App/lib/core/Gen/Expr/ExprGenAttr.vp +++ b/App/lib/core/Gen/Expr/ExprGenAttr.vp @@ -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: diff --git a/App/lib/core/Gen/Expr/ExprGenCls.vp b/App/lib/core/Gen/Expr/ExprGenCls.vp index 920af50..81359d1 100644 --- a/App/lib/core/Gen/Expr/ExprGenCls.vp +++ b/App/lib/core/Gen/Expr/ExprGenCls.vp @@ -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. diff --git a/App/lib/core/Gen/Expr/ExprGenName.vp b/App/lib/core/Gen/Expr/ExprGenName.vp index 903ef21..8893a83 100644 --- a/App/lib/core/Gen/Expr/ExprGenName.vp +++ b/App/lib/core/Gen/Expr/ExprGenName.vp @@ -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) diff --git a/App/lib/core/Gen/TranslatorGenMixin.vp b/App/lib/core/Gen/TranslatorGenMixin.vp index e7e26e0..2122ce0 100644 --- a/App/lib/core/Gen/TranslatorGenMixin.vp +++ b/App/lib/core/Gen/TranslatorGenMixin.vp @@ -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. node in a global type annotation; None when absent.""" + """Find the attr. 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 diff --git a/App/lib/core/Gen/Type/TypeGenErr.vp b/App/lib/core/Gen/Type/TypeGenErr.vp index 3b814c8..73f3ee5 100644 --- a/App/lib/core/Gen/Type/TypeGenErr.vp +++ b/App/lib/core/Gen/Type/TypeGenErr.vp @@ -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) diff --git a/App/lib/core/Gen/Type/TypeGenResolve.vp b/App/lib/core/Gen/Type/TypeGenResolve.vp index 2e4bb58..6e6038d 100644 --- a/App/lib/core/Gen/Type/TypeGenResolve.vp +++ b/App/lib/core/Gen/Type/TypeGenResolve.vp @@ -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 "" + 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 "" + 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 "" - # 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 "" - return "" 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.[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.) 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)