diff --git a/lib/core/LLVMTypeMapper.py b/lib/core/LLVMTypeMapper.py index d96b093..9e5cd41 100644 --- a/lib/core/LLVMTypeMapper.py +++ b/lib/core/LLVMTypeMapper.py @@ -26,6 +26,13 @@ from lib.includes import t as _t from lib.includes.t import CTypeRegistry +# typedef 值位置的类型限定符标记:它们自身不是类型(名下有 const/volatile 语义, +# 由注解解析器累积),在 typedef 值里只修饰 `|` 另一侧。历史写法 +# `t.CConst | char | t.CPtr` 恰好被 `char` 一侧掩盖;新语法 `const str` +# (= `t.CConst | str`)暴露了这一点,故显式判为"无类型信息"。 +_TYPEDEF_VALUE_QUALIFIERS: frozenset[str] = frozenset({'CConst', 'CVolatile'}) + + class TypeNameUnresolvedError(NameError): """类型注解名无法解析(fail-fast,取代旧的"warn + 回退 i32")。 @@ -208,6 +215,12 @@ class LLVMTypeMapper: # 直接判断属性名是否为 CPtr(消除对 CName=='*' 的依赖) if node.attr == 'CPtr': return 'ptr' + if node.attr in _TYPEDEF_VALUE_QUALIFIERS: + # 类型限定符标记(const/volatile)本身不是类型:它只修饰 `|` 另一侧。 + # 旧写法 `t.CConst | char | t.CPtr` 靠 char 一侧掩盖了这一点;一旦写成 + # `t.CConst | str`(`const str`),把 'CConst' 当基类型会得到 'CConst *', + # 于是别名登记失败、所有使用处报「裸名 … 无法解析为类型」。 + return '' CName: str | None = CTypeHelper.GetCName(node.attr) if CName == 'CPtr': return 'ptr' @@ -217,15 +230,23 @@ class LLVMTypeMapper: if isinstance(node, ast.BinOp) and isinstance(node.op, ast.BitOr): left_str: str = self.resolve_typedef_value_type(node.left) right_str: str = self.resolve_typedef_value_type(node.right) - if right_str == 'ptr' or (right_str and right_str.endswith('*')): - base: str = left_str if left_str and left_str != 'ptr' else '' - return base + ' *' if base else 'void *' - if left_str == 'ptr' or (left_str and left_str.endswith('*')): - base: str = right_str if right_str and right_str != 'ptr' else '' - return base + ' *' if base else 'void *' - if right_str and right_str not in ('ptr', 'void', 'i32', 'i64'): + # 限定符侧解析为空串 ⇒ 另一侧就是完整类型(见 _TYPEDEF_VALUE_QUALIFIERS)。 + # 只在两侧都有类型信息时才按"基类型 + 指针标记"合并。 + if not left_str: + return 'void *' if right_str == 'ptr' else right_str + if not right_str: + return 'void *' if left_str == 'ptr' else left_str + if right_str == 'ptr' or right_str.endswith('*'): + if left_str != 'ptr': + return left_str + ' *' + return right_str if right_str.endswith('*') else 'void *' + if left_str == 'ptr' or left_str.endswith('*'): + if right_str != 'ptr': + return right_str + ' *' + return left_str if left_str.endswith('*') else 'void *' + if right_str not in ('ptr', 'void', 'i32', 'i64'): return right_str - if left_str and left_str not in ('ptr', 'void', 'i32', 'i64'): + if left_str not in ('ptr', 'void', 'i32', 'i64'): return left_str return left_str if left_str else right_str return '' diff --git a/lib/core/SymbolUtils.py b/lib/core/SymbolUtils.py index c1198e7..2261037 100644 --- a/lib/core/SymbolUtils.py +++ b/lib/core/SymbolUtils.py @@ -449,6 +449,54 @@ def _StaticPositionSyntaxError(Lineno: int, Col: int, Snippet: str) -> SyntaxErr ) +_TYPEDEF_RHS_SCAN_LIMIT: int = 256 + + +def _IsTypedefAnnotationRhs(Tokens: list, EqIdx: int) -> bool: + """`EqIdx` 处的 `=` 是否是 `名字: <含 CTypedef 的注解> = <类型>` 的赋值号。 + + 用途:把 `t.CTypedef` 注解的**右值**也纳入类型位置,使 + `LPCSTR: t.CTypedef = const str` / `= const uint16*` 里的描述符被展开 + (与注解位同一展开规则;右值的 `T*` 后缀由 vast 的 _ParseTypeAnnotation 处理)。 + + 判据:从 `=` 反向扫描到**深度 0 的 `:`**(跳过注释/NL,遇换行/`;` 即视为非声明行), + 再确认 `:` 与 `=` 之间出现 NAME `CTypedef`。故 `x: int = const`(值恰为标识符 const)、 + `d = {"k": const}`、`f = lambda x: t.CTypedef` 等一律不受影响。 + """ + if EqIdx < 0 or EqIdx >= len(Tokens): + return False + depth: int = 0 + colon_idx: int = -1 + j: int = EqIdx - 1 + steps: int = 0 + while j >= 0 and steps < _TYPEDEF_RHS_SCAN_LIMIT: + steps += 1 + tok = Tokens[j] + if tok.type in (tokenize.NL, tokenize.COMMENT): + j -= 1 + continue + if tok.type in (tokenize.NEWLINE, tokenize.INDENT, tokenize.DEDENT) or tok.string == ';': + return False + if tok.type == tokenize.OP: + if tok.string in (')', ']', '}'): + depth += 1 + elif tok.string in ('(', '[', '{'): + if depth == 0: + return False + depth -= 1 + elif tok.string == ':' and depth == 0: + colon_idx = j + break + j -= 1 + if colon_idx < 0: + return False + for k in range(colon_idx + 1, EqIdx): + tk = Tokens[k] + if tk.type == tokenize.NAME and tk.string == 'CTypedef': + return True + return False + + def _InStaticOldAnnotationPosition(Tokens: list, Index: int) -> bool: """`Index` 处的 `static` 是否处于**旧**注解位置(`名字: static 类型` / `-> static 类型`)。 @@ -1103,7 +1151,8 @@ def _ExpandTypeDescriptors(Code: str) -> str: """把 `const T` / `volatile T`(含组合、任意顺序)展开为 `t.CConst | T` 等形态(行号不变)。 - 词法级切分(`tokenize`):注释/字符串内容天然不参与,不会被误改写。 - - 只在"注解位置"(前一个 token 是 `:` 且再前一个是 Name,或前一个 token 是 `->`) + - 只在"注解位置"(前一个 token 是 `:` 且再前一个是 Name,或前一个 token 是 `->`, + 或 `名字: t.CTypedef =` 的赋值号右侧,见 _IsTypedefAnnotationRhs) 识别描述符串;非类型位置原样保留为普通标识符(软关键字边界)。 - **`static` 不再属于本展开器**(已迁出注解位置):注解位置出现 `static` (如 `memory_regions: static memory_region* = None`)一律**响亮失败** @@ -1155,6 +1204,10 @@ def _ExpandTypeDescriptors(Code: str) -> str: BeforePrevIdx: int = _PrevSignificantTokenIndex(Tokens, PrevIdx) if BeforePrevIdx >= 0 and Tokens[BeforePrevIdx].type == tokenize.NAME: InAnnotationPosition = True + elif Prev.string == '=' and _IsTypedefAnnotationRhs(Tokens, PrevIdx): + # `LPCSTR: t.CTypedef = const str`:typedef 的右值也是类型位置 + # (与 TPV 的 _parse_type_ann 右值分支同口径)。 + InAnnotationPosition = True if InAnnotationPosition: if Run[0].start[0] != Run[-1].end[0]: # 描述符串跨行:本层只支持同行(不增删换行的前提),响亮失败不静默丢 diff --git a/lib/core/TypeAnnotationResolver.py b/lib/core/TypeAnnotationResolver.py index 09a0ee2..0a8e227 100644 --- a/lib/core/TypeAnnotationResolver.py +++ b/lib/core/TypeAnnotationResolver.py @@ -149,15 +149,46 @@ class TypeAnnotationResolver: _TYPEDEF_SUGAR_RE: re.Pattern = re.compile( r'^typedef\s+([A-Za-z_]\w*)\s*=(?!=)\s*(.+?)\s*$') + @staticmethod + def _NormalizeTypedefRhsText(text: str) -> str: + """把 typedef 右值的 C 式写法归一成规范 `|` 联合文本(供**文本回退路径**使用)。 + + 磁盘原文里 `X: t.CTypedef = const str` / `= const uint16*` 的右值带描述符与指针 + 后缀,而本路径(_parse_typedef_line → _resolve_builtins_type_str)按表达式文本解析, + 故先归一: + - 前导 `const` / `volatile`(可组合) → `t.CConst` / `t.CVolatile` 前缀; + - 末尾 `*` / `**` 串 → 逐层 `| t.CPtr`。 + 不含上述形态的文本原样返回(幂等)。无算术语义风险:typedef 右值不含乘法。 + """ + s: str = text.strip() + if not s: + return text + markers: list[str] = [] + while True: + m: re.Match | None = re.match(r'^(const|volatile)\s+', s) + if m is None: + break + markers.append('t.CConst' if m.group(1) == 'const' else 't.CVolatile') + s = s[m.end():].lstrip() + ptrs: int = 0 + while s.endswith('*'): + s = s[:-1].rstrip() + ptrs += 1 + if not markers and ptrs == 0: + return text + parts: list[str] = markers + [s] + ['t.CPtr'] * ptrs + return ' | '.join(p for p in parts if p) + @staticmethod def _parse_typedef_line(stripped: str) -> tuple[str, str] | None: """单行源码文本 → typedef 声明 (名字, 类型值);非 typedef 行返回 None 兼容 `typedef 名字 = 类型`(语法糖)与 `名字: t.CTypedef = 类型`(注解)。 + 右值的 C 式写法(`const T` / `T*`)经 _NormalizeTypedefRhsText 归一为 `|` 联合。 """ m: re.Match | None = TypeAnnotationResolver._TYPEDEF_SUGAR_RE.match(stripped) if m is not None: - return m.group(1), m.group(2) + return m.group(1), TypeAnnotationResolver._NormalizeTypedefRhsText(m.group(2)) if ':' not in stripped or 't.CTypedef' not in stripped: return None name, _, rest = stripped.partition(':') @@ -167,7 +198,7 @@ class TypeAnnotationResolver: name = name.strip() if not name or not value: return None - return name, value + return name, TypeAnnotationResolver._NormalizeTypedefRhsText(value) @staticmethod def _builtins_include_dirs() -> list[str]: diff --git a/lib/vast/Parser.py b/lib/vast/Parser.py index 25ef138..be0533e 100644 --- a/lib/vast/Parser.py +++ b/lib/vast/Parser.py @@ -373,11 +373,34 @@ class Parser(ExprParserMixin): self._SetStoreContext(expr) return expr + @staticmethod + def _IsCTypedefAnnotation(annotation: N.expr | None) -> bool: + """注解是否为 typedef 标记(裸 `CTypedef` / `t.CTypedef` / 含它的 `|` 联合)。 + + 用于把 typedef 的右值按**类型位置**解析(`X: t.CTypedef = const uint16*` + 里的 `*` 后缀由 _ParseTypeAnnotation 消费,与注解位同一规则;描述符 + `const`/`volatile` 已由 SymbolUtils._ExpandTypeDescriptors 在文本层展开)。 + 仅按标记名判定,不依赖 import 别名表(与 TPV `_IsCTypedefAnnotation` 同口径)。 + """ + if annotation is None: + return False + if isinstance(annotation, N.Attribute): + return getattr(annotation, 'attr', None) == 'CTypedef' + if isinstance(annotation, N.Name): + return getattr(annotation, 'id', None) == 'CTypedef' + if isinstance(annotation, N.BinOp): + return (Parser._IsCTypedefAnnotation(getattr(annotation, 'left', None)) + or Parser._IsCTypedefAnnotation(getattr(annotation, 'right', None))) + return False + def _TryParseAnnAssign(self) -> N.AnnAssign | None: """尝试解析带注解赋值:x: T [= v] 或 x: T(目标为 Name/Attribute/Subscript)。 注意:仅当目标为简单 Name/Attribute/Subscript 且后随 ':' 时匹配; 元组/列表解包不做注解(返回 None 走普通路径)。 + + `t.CTypedef` 注解的右值是**类型**而非一般表达式(`const str` / `uint16*`), + 故走 `_ParseTypeAnnotation`(与注解位同一路径,支持 C 式指针后缀)。 """ start: Token = self._Peek() # 名称 @@ -388,7 +411,10 @@ class Parser(ExprParserMixin): value: N.expr | None = None if self._AtOp('='): self._Next() - value = self.ParseExpr() + if self._IsCTypedefAnnotation(annotation): + value = self._ParseTypeAnnotation() + else: + value = self.ParseExpr() target: N.Name = N.Name(id=name_tok.Value, ctx=N.Store()) self._FixLoc(target, name_tok) node: N.AnnAssign = N.AnnAssign(target=target, annotation=annotation, @@ -409,7 +435,10 @@ class Parser(ExprParserMixin): value = None if self._AtOp('='): self._Next() - value = self.ParseExpr() + if self._IsCTypedefAnnotation(annotation): + value = self._ParseTypeAnnotation() + else: + value = self.ParseExpr() self._SetStoreContext(expr) simple: int = 1 if isinstance(expr, N.Name) else 0 node = N.AnnAssign(target=expr, annotation=annotation, value=value, simple=simple) diff --git a/lib/vast_c/_parser.pyd b/lib/vast_c/_parser.pyd index 0ba5c95..9e5184d 100644 Binary files a/lib/vast_c/_parser.pyd and b/lib/vast_c/_parser.pyd differ diff --git a/lib/vast_c/vast_c.c b/lib/vast_c/vast_c.c index 7fa8716..46e47fb 100644 --- a/lib/vast_c/vast_c.c +++ b/lib/vast_c/vast_c.c @@ -4651,6 +4651,50 @@ static PyObject *ParseSimpleStmt(Parser *P) { return Node; } +/* 注解是否为 typedef 标记(裸 CTypedef / t.CTypedef / 含它的 `|` 联合)。 + * 用于把 typedef 的右值按类型位置解析(`X: t.CTypedef = const uint16*` 的 `*` 后缀 + * 由 ParseTypeAnnotation 消费),与 Python vast Parser._IsCTypedefAnnotation 同口径。 */ +static int IsCTypedefAnnotation(PyObject *Ann) { + if (Ann == NULL || Ann == Py_None) { + return 0; + } + if (NodeIs(Ann, "Name") || NodeIs(Ann, "Attribute")) { + const char *Field = NodeIs(Ann, "Name") ? "id" : "attr"; + PyObject *V = PyObject_GetAttrString(Ann, Field); + int Hit = 0; + if (V != NULL) { + if (PyUnicode_Check(V) && PyUnicode_CompareWithASCIIString(V, "CTypedef") == 0) { + Hit = 1; + } + Py_DECREF(V); + } else { + PyErr_Clear(); + } + return Hit; + } + if (NodeIs(Ann, "BinOp")) { + int Hit = 0; + PyObject *L = PyObject_GetAttrString(Ann, "left"); + if (L != NULL) { + Hit = IsCTypedefAnnotation(L); + Py_DECREF(L); + } else { + PyErr_Clear(); + } + if (!Hit) { + PyObject *R = PyObject_GetAttrString(Ann, "right"); + if (R != NULL) { + Hit = IsCTypedefAnnotation(R); + Py_DECREF(R); + } else { + PyErr_Clear(); + } + } + return Hit; + } + return 0; +} + /* 尝试解析带注解赋值(对齐 vast _TryParseAnnAssign) */ static PyObject *TryParseAnnAssign(Parser *P) { Token *Start = PeekTok(P, 0); @@ -4666,7 +4710,8 @@ static PyObject *TryParseAnnAssign(Parser *P) { PyObject *Value = NULL; if (AtOp(P, "=")) { NextTok(P); - Value = ParseExpr(P); + /* A typedef's RHS is a type (`const str` / `uint16*`), not a general expression. */ + Value = IsCTypedefAnnotation(Annotation) ? ParseTypeAnnotation(P) : ParseExpr(P); if (Value == NULL) { Py_DECREF(Annotation); return NULL; @@ -4712,7 +4757,8 @@ static PyObject *TryParseAnnAssign(Parser *P) { PyObject *Value = NULL; if (AtOp(P, "=")) { NextTok(P); - Value = ParseExpr(P); + /* A typedef's RHS is a type (`const str` / `uint16*`). */ + Value = IsCTypedefAnnotation(Annotation) ? ParseTypeAnnotation(P) : ParseExpr(P); if (Value == NULL) { Py_DECREF(Annotation); Py_DECREF(Expr);