diff --git a/ast/parser.vp b/ast/parser.vp index f8c953d..6e83662 100644 --- a/ast/parser.vp +++ b/ast/parser.vp @@ -428,42 +428,74 @@ def _MakeDescriptorMarker(ps: Parser*) -> AST*: return marker +def _MakeTypeWrapper(ps: Parser*, marker: AST*, inner: AST*) -> AST*: + """Wrap a type in a builtin marker subscript: marker[inner] (e.g. CPtr[T], CConst[T]).""" + node: AST* = Subscript(ps.pool, marker, inner, ASTCtx.Load) + if inner is not None: + _set_pos(node, inner.lineno, inner.col_offset, 0, 0) + return node + + def _PrependDescriptorMarkers(ps: Parser*, base: AST*, mods: list[AST*]*, mod_count: int) -> AST*: - """Prepend descriptor markers to the leftmost operand of the type union (same shape as TPC text). + """Wrap the type in the descriptor markers collected before it. - TPC expands `const volatile int | rawptr` to the left-associative `(((t.CConst | t.CVolatile) | int) | rawptr)`; - this function produces the identical tree rather than a differently-shaped one. + `const volatile int` becomes `CConst[CVolatile[int]]`; the marker order is irrelevant to the + backend, which only asks whether a given descriptor is present. """ - slot_parent: BinOp* = None # leftmost BitOr node: its left is the leftmost operand - slot_node: AST* = base - cur: AST* = base - while cur is not None and cur.kind() == ASTKind.BinOp: - b: BinOp* = (BinOp*)(cur) - if b.op != OpKind.BitOr or b.left is None: - break - nxt: AST* = b.left - if nxt.kind() == ASTKind.BinOp: - nb: BinOp* = (BinOp*)(nxt) - if nb.op == OpKind.BitOr and nb.left is not None: - cur = nxt - continue - slot_parent = b - slot_node = nxt - break - # Descriptor chain left-associates in source order: `const volatile` -> (t.CConst | t.CVolatile) - chain: AST* = mods.get(0) - i: int = 1 - while i < mod_count: - chain = BinOp(ps.pool, chain, OpKind.BitOr, mods.get(i)) - i += 1 - newnode: AST* = BinOp(ps.pool, chain, OpKind.BitOr, slot_node) - if slot_parent is None: - return newnode - slot_parent.left = newnode - return base + out: AST* = base + i: int = mod_count - 1 + while i >= 0: + out = _MakeTypeWrapper(ps, mods.get(i), out) + i -= 1 + return out + + +def _RecordTypeUnionErr(detail: str, lineno: int, col: int): + """Record a retired `T1 | T2` type union (fail-fast; first only). + + All former union forms have dedicated syntax: T* / CPtr[T] for pointers, const/volatile/bigend/ + littlend for descriptors, &T / &temp T for value references, export/extern/static for storage. + """ + global _g_parse_err_line, _g_parse_err_col, _g_parse_err_msg + if _g_parse_err_line != 0: + return + _g_parse_err_line = lineno + _g_parse_err_col = col + buf: str = stdlib.malloc(1024) + if buf is not None: + stdio.snprintf(buf, 1024, + "`T1 | T2` type unions were removed (%s). Use the dedicated syntax instead: " + "pointer -> `T*`; const/volatile/bigend/littlend -> the prefix descriptor; " + "value reference -> `&T` / `&temp T`; storage -> `export` / `extern` / `static` / `def`; " + "fixed array -> `[N]T` or `CArray[T, N]`.", detail) + _g_parse_err_msg = buf + else: + _g_parse_err_msg = detail + + +def _RejectTypeUnion(ps: Parser*, node: AST*) -> int: + """Fail-fast when an annotation contains a retired `|` type union; 1=rejected.""" + if node is None: return 0 + k: int = node.kind() + if k == ASTKind.BinOp: + bo: BinOp* = (BinOp*)(node) + if bo.op == OpKind.BitOr: + _RecordTypeUnionErr("`|` is not allowed in a type annotation", node.lineno, node.col_offset) + ps.error_count += 1 + return 1 + return 0 + if k == ASTKind.Subscript: + sub_u: Subscript* = (Subscript*)(node) + return _RejectTypeUnion(ps, sub_u.slice) + if k == ASTKind.Tuple: + tup_u: Tuple* = (Tuple*)(node) + if tup_u.elts is not None: + for ui in range(int(len(tup_u.elts))): + if _RejectTypeUnion(ps, tup_u.elts.get(ui)) != 0: return 1 + return 0 def _RecordDescriptorErr(detail: str, lineno: int, col: int): @@ -1491,9 +1523,9 @@ def _at_ptr_suffix(ps: Parser*, base: AST*) -> int: def _make_ptr_type(ps: Parser*, base: AST*) -> AST*: - """Build one pointer wrapper layer: base | CPtr (builtin pointer marker).""" + """Build one pointer wrapper layer: CPtr[base] (builtin pointer marker).""" cptr: AST* = Name(ps.pool, "CPtr", ASTCtx.Load) - node: AST* = BinOp(ps.pool, base, OpKind.BitOr, cptr) + node: AST* = _MakeTypeWrapper(ps, cptr, base) _set_pos(node, base.lineno, base.col_offset, 0, 0) return node @@ -2611,27 +2643,16 @@ def _parse_array_prefix_declarator(ps: Parser*) -> AST*: def _IsCTypedefAnnotation(ann: AST*) -> int: - """1 if the annotation is a typedef marker (bare CTypedef / t.CTypedef / a union containing it). + """1 if the annotation is the bare CTypedef marker. - Used to parse a typedef's right-hand side as a **type**: `X: t.CTypedef = const uint16*` + Used to parse a typedef's right-hand side as a **type**: `typedef X = const uint16*` synthesizes descriptors and the `*` suffix through _parse_type_ann (the annotation rule), so both sides agree. TPC's vast Parser._IsCTypedefAnnotation uses the same name-based test. """ if ann is None: return 0 - kd: int = ann.kind() - if kd == ASTKind.Attribute: - at: Attribute* = Attribute*(ann) - if at.attr is not None and string.strcmp(at.attr, "CTypedef") == 0: return 1 - return 0 - if kd == ASTKind.Name: - nm: Name* = Name*(ann) - if nm.id is not None and string.strcmp(nm.id, "CTypedef") == 0: return 1 - return 0 - if kd == ASTKind.BinOp: - bo: BinOp* = BinOp*(ann) - if bo.left is not None and _IsCTypedefAnnotation(bo.left) != 0: return 1 - if bo.right is not None and _IsCTypedefAnnotation(bo.right) != 0: return 1 - return 0 + if ann.kind() != ASTKind.Name: return 0 + nm: Name* = Name*(ann) + if nm.id is not None and string.strcmp(nm.id, "CTypedef") == 0: return 1 return 0 @@ -2742,6 +2763,9 @@ def _parse_type_ann(ps: Parser*) -> AST*: _RecordDescriptorErr("missing type after descriptor (type parse failed)", mod_lineno, mod_col) _record_parse_err(ps) # annotation parse failed: record position (error_count already bumped in _parse_test) return None + # `T1 | T2` unions were retired: every former union form has dedicated syntax. + if _RejectTypeUnion(ps, base) != 0: + return None if mod_count > 0 and base.kind() == ASTKind.Constant: # Non-type follower (`a: const 1` / `a: const "x"` / `a: const True`): fail loudly, since # descriptors modify types only and constants are not types -- never silently degrade or drop. @@ -2781,17 +2805,17 @@ def _parse_type_ann(ps: Parser*) -> AST*: while _match_op(ps, TokOp.Star): _advance(ps) ptr_level += 1 - # Build n nested base | CPtr layers + # Build n nested CPtr[base] layers for li_an in range(int(ptr_level)): cptr_an: AST* = Name(ps.pool, "CPtr", ASTCtx.Load) - base = BinOp(ps.pool, base, OpKind.BitOr, cptr_an) - # Paste descriptors last (same shape as TPC text expansion; see _PrependDescriptorMarkers) + base = _MakeTypeWrapper(ps, cptr_an, base) + # Paste descriptors last (outermost wrappers) if mod_count > 0: base = _PrependDescriptorMarkers(ps, base, mods, mod_count) - # Append the value-reference marker as the outermost union member: `base | CRef` / `base | CTempRef`. + # Append the value-reference marker as the outermost wrapper: CRef[base] / CTempRef[base]. if ref_marker is not None: ref_marker_node: AST* = Name(ps.pool, ref_marker, ASTCtx.Load) - base = BinOp(ps.pool, base, OpKind.BitOr, ref_marker_node) + base = _MakeTypeWrapper(ps, ref_marker_node, base) return base @@ -3770,27 +3794,27 @@ def _parse_funcdef(ps: Parser*, is_async: int) -> AST*: if _accept_op(ps, TokOp.RArrow): returns = _parse_type_ann(ps) if is_extern_fd: - # extern declaration: rewrite the return annotation to | State, reusing the existing - # State/external-C-function path (vpi annotation containing "State" -> bare name, no function body) + # extern declaration: wrap the return annotation as State[T], reusing the existing + # State/external-C-function path (a lone State marker -> Void, no function body) state_attr_fd: AST* = Name(ps.pool, "State", ASTCtx.Load) if returns != None: - returns = BinOp(ps.pool, returns, OpKind.BitOr, state_attr_fd) + returns = _MakeTypeWrapper(ps, state_attr_fd, returns) else: returns = state_attr_fd elif is_export_fd: - # export declaration: rewrite the return annotation to | CExport, reusing the existing - # CExport path (vpi annotation containing "CExport" -> bare name, no module SHA1 header) + # export declaration: wrap the return annotation as CExport[T], reusing the existing + # CExport path (bare-name symbol, no module SHA1 header) export_attr_fd: AST* = Name(ps.pool, "CExport", ASTCtx.Load) if returns != None: - returns = BinOp(ps.pool, returns, OpKind.BitOr, export_attr_fd) + returns = _MakeTypeWrapper(ps, export_attr_fd, returns) else: returns = export_attr_fd elif is_define_fd: - # define macro function: rewrite the return annotation to | CDefine, reusing the - # existing CDefine path (StubGen keeps it as-is -> C #define / alwaysinline) + # define macro function: wrap the return annotation as CDefine[T], reusing the existing + # CDefine path (StubGen keeps it as-is -> C #define / alwaysinline) define_attr_fd: AST* = Name(ps.pool, "CDefine", ASTCtx.Load) if returns != None: - returns = BinOp(ps.pool, returns, OpKind.BitOr, define_attr_fd) + returns = _MakeTypeWrapper(ps, define_attr_fd, returns) else: returns = define_attr_fd dec_list: list[AST*]* = ps.pending_decorators @@ -3977,7 +4001,7 @@ def _parse_define_const(ps: Parser*) -> AST*: cdefine_marker: AST* = Name(ps.pool, "CDefine", ASTCtx.Load) annotation: AST* = cdefine_marker if explicit_ann is not None: - annotation = BinOp(ps.pool, explicit_ann, OpKind.BitOr, cdefine_marker) + annotation = _MakeTypeWrapper(ps, cdefine_marker, explicit_ann) target: AST* = Name(ps.pool, name, ASTCtx.Store) _set_pos(target, lineno, col, 0, 0) node: AST* = AnnAssign(ps.pool, target, annotation, value, 1) @@ -4062,9 +4086,9 @@ def _parse_export_extern_var(ps: Parser*) -> AST*: ps.error_count += 1 _record_parse_err(ps) return None - # Storage-class marker: export -> CExport (bare-name export); extern -> State (declaration + bare name) + # Storage-class marker: export -> CExport[T] (bare-name export); extern -> State[T] (declaration + bare name) storage_attr: AST* = Name(ps.pool, "State" if is_extern != 0 else "CExport", ASTCtx.Load) - annotation: AST* = BinOp(ps.pool, ann, OpKind.BitOr, storage_attr) + annotation: AST* = _MakeTypeWrapper(ps, storage_attr, ann) value: AST* = None if _match_op(ps, TokOp.Equal): if is_extern != 0: