Files
TransPyC/lib/vast/Unparser.py
T
TermiNexus 660a15bef8 refactor(tpc): 删除类型联合语法并将修饰迁移为 AST 字段
类型注解不再接受任何 T1 | T2;const/volatile/static/export/extern/inline/define/typedef/__attribute__/BigEndian/LittleEndian/Bit 改为注解节点字段 Markers/Attributes/ByteOrder/BitWidth;指针与引用改为 CPtr[T]/CRef[T]/CTempRef[T]/CArrayPtr[T]/CAutoPtr[T];Python 与 vast_c 两套解析器、类型解析与声明端同步;C 扩展早于 vast_c.c 时自动回退纯 Python 解析器。
2026-10-18 13:35:34 +08:00

626 lines
26 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""vast.unparser - AST 还原为源码字符串(与 ast.unparse 语义一致)
TPC 编译器多处用 ``ast.unparse`` 提取类型注解字符串(如 ``CArray[...]``、
``list[i32, N]``、数组维度、默认值文本等)。本模块实现全部节点类型的
源码还原,保证 ``import vast as ast`` 后这些调用零修改。
"""
from __future__ import annotations
from lib.vast import Nodes as N
class Unparser:
"""递归将 vast 节点还原为源码文本。"""
# 语句级修饰词:由 _AnnAssign 渲染在目标名之前(而非类型前)。
_STATEMENT_MARKERS: frozenset[str] = frozenset({
'static', 'export', 'extern', 'define', 'typedef',
})
# 未显式标注类型的 define 常量以 CDefine 作为注解占位,还原时省略类型部分。
_IMPLICIT_ANNOTATION_NAMES: frozenset[str] = frozenset({'CDefine'})
def __init__(self) -> None:
self._Depth: int = 0
# 当前正在渲染的注解上被语句级修饰词消费掉的 Markers(不再输出为类型前缀)。
self._SuppressedMarkers: frozenset[str] = frozenset()
# ------------------------------------------------------------------
# 顶层
# ------------------------------------------------------------------
def Unparse(self, node: N.AST) -> str:
if isinstance(node, N.Module):
return '\n'.join(self._stmt(s) for s in node.body)
if isinstance(node, N.Expression):
return self._expr(node.body)
return self._expr(node) if isinstance(node, N.expr) else self._stmt(node) # type: ignore[arg-type]
# ------------------------------------------------------------------
# 语句
# ------------------------------------------------------------------
def _stmt(self, node: N.AST) -> str:
name: str = type(node).__name__
m: dict[str, object] = {
'FunctionDef': self._FunctionDef,
'AsyncFunctionDef': self._FunctionDef,
'ClassDef': self._ClassDef,
'Return': self._Return,
'Delete': self._Delete,
'Assign': self._Assign,
'AugAssign': self._AugAssign,
'AnnAssign': self._AnnAssign,
'For': self._For,
'AsyncFor': self._For,
'While': self._While,
'If': self._If,
'With': self._With,
'AsyncWith': self._With,
'Raise': self._Raise,
'Try': self._Try,
'TryStar': self._Try,
'Assert': self._Assert,
'Import': self._Import,
'ImportFrom': self._ImportFrom,
'Global': self._Global,
'Nonlocal': self._Global,
'Expr': self._ExprStmt,
'Pass': self._Pass,
'Break': lambda n: 'break',
'Continue': lambda n: 'continue',
'Match': self._Match,
}
fn = m.get(name)
if fn is not None:
return fn(node) # type: ignore[operator]
raise NotImplementedError(f'unparse stmt: {name}')
def _Block(self, body: list[N.stmt]) -> str:
self._Depth += 1
pad: str = ' ' * self._Depth
lines: list[str] = [f'{pad}{self._stmt(s)}' for s in body]
self._Depth -= 1
return '\n'.join(lines)
def _FunctionDef(self, node: N.FunctionDef) -> str:
is_extern: bool = bool(getattr(node, 'Extern', False))
is_export: bool = bool(getattr(node, 'Export', False))
is_define: bool = bool(getattr(node, 'Define', False))
if is_extern:
kw: str = 'extern def'
elif is_export:
kw = 'export def'
elif is_define:
kw = 'define'
else:
kw = 'async def' if isinstance(node, N.AsyncFunctionDef) else 'def'
decos: str = ''.join(f'@{self._expr(d)}\n' for d in node.decorator_list)
params: str = self._Arguments(node.args)
ret: str = ''
returns: N.expr | None = node.returns
if returns is not None:
ret = f' -> {self._expr(returns)}'
body: str = self._Block(node.body)
return f'{decos}{kw} {node.name}({params}){ret}:\n{body}'
def _ClassDef(self, node: N.ClassDef) -> str:
decos: str = ''.join(f'@{self._expr(d)}\n' for d in node.decorator_list)
args: list[str] = [self._expr(b) for b in node.bases]
for kw in node.keywords:
args.append(f'{kw.arg}={self._expr(kw.value)}' if kw.arg else f'**{self._expr(kw.value)}')
header: str = f'class {node.name}' + (f'({", ".join(args)})' if args else '') + ':'
return f'{decos}{header}\n{self._Block(node.body)}'
def _Return(self, node: N.Return) -> str:
return 'return' if node.value is None else f'return {self._expr(node.value)}'
def _Delete(self, node: N.Delete) -> str:
return 'del ' + ', '.join(self._expr(t) for t in node.targets)
def _Assign(self, node: N.Assign) -> str:
return ', '.join(self._expr(t) for t in node.targets) + f' = {self._expr(node.value)}'
def _AugAssign(self, node: N.AugAssign) -> str:
ops: dict[type, str] = {
N.Add: '+', N.Sub: '-', N.Mult: '*', N.Div: '/', N.FloorDiv: '//',
N.Mod: '%', N.Pow: '**', N.LShift: '<<', N.RShift: '>>',
N.BitOr: '|', N.BitXor: '^', N.BitAnd: '&', N.MatMult: '@',
}
op: str = ops.get(type(node.op), '?')
return f'{self._expr(node.target)} {op}= {self._expr(node.value)}'
def _AnnAssign(self, node: N.AnnAssign) -> str:
annotation: N.expr = node.annotation
markers: tuple[str, ...] = tuple(getattr(annotation, 'Markers', ()) or ())
saved: frozenset[str] = self._SuppressedMarkers
self._SuppressedMarkers = frozenset(
m for m in markers if m in self._STATEMENT_MARKERS)
try:
type_text: str = self._expr(annotation)
target_text: str = self._expr(node.target)
if 'typedef' in markers:
rhs: str = (self._expr(node.value) if node.value is not None
else type_text)
return f'typedef {target_text} = {rhs}'
if 'define' in markers:
head: str = f'define {target_text}'
implicit: bool = (
isinstance(annotation, N.Name)
and annotation.id in self._IMPLICIT_ANNOTATION_NAMES)
if not implicit:
head += f': {type_text}'
if node.value is not None:
head += f' = {self._expr(node.value)}'
return head
stmt_prefix: str = ''.join(
f'{m} ' for m in ('extern', 'export', 'static') if m in markers)
val: str = f' = {self._expr(node.value)}' if node.value is not None else ''
return f'{stmt_prefix}{target_text}: {type_text}{val}'
finally:
self._SuppressedMarkers = saved
def _TypeModifierPrefix(self, node: N.AST) -> str:
"""把注解节点的 Markers / Attributes / ByteOrder / BitWidth 还原为前缀关键字。"""
parts: list[str] = [
m for m in (getattr(node, 'Markers', ()) or ())
if m not in self._SuppressedMarkers
]
attrs: tuple[str, ...] = tuple(getattr(node, 'Attributes', ()) or ())
if attrs:
parts.append('__attribute__((' + ', '.join(attrs) + '))')
order: str = getattr(node, 'ByteOrder', '') or ''
if order == 'big':
parts.append('BigEndian')
elif order == 'little':
parts.append('LittleEndian')
width: int = getattr(node, 'BitWidth', 0) or 0
if width:
parts.append(f'Bit({width})')
return ''.join(f'{p} ' for p in parts)
def _WithTypeModifiers(self, node: N.AST, text: str) -> str:
return self._TypeModifierPrefix(node) + text
def _For(self, node: N.For) -> str:
kw: str = 'async for' if isinstance(node, N.AsyncFor) else 'for'
orelse: str = f'\n{" " * self._Depth}else:\n{self._Block(node.orelse)}' if node.orelse else ''
return f'{kw} {self._expr(node.target)} in {self._expr(node.iter)}:\n{self._Block(node.body)}{orelse}'
def _While(self, node: N.While) -> str:
orelse: str = f'\n{" " * self._Depth}else:\n{self._Block(node.orelse)}' if node.orelse else ''
return f'while {self._expr(node.test)}:\n{self._Block(node.body)}{orelse}'
def _If(self, node: N.If) -> str:
out: str = f'if {self._expr(node.test)}:\n{self._Block(node.body)}'
if node.orelse:
if len(node.orelse) == 1 and isinstance(node.orelse[0], N.If):
out += f'\n{" " * self._Depth}elif {self._expr(node.orelse[0].test)}:\n{self._Block(node.orelse[0].body)}'
out += self._IfTail(node.orelse[0])
else:
out += f'\n{" " * self._Depth}else:\n{self._Block(node.orelse)}'
return out
def _IfTail(self, node: N.If) -> str:
out: str = ''
if node.orelse:
if len(node.orelse) == 1 and isinstance(node.orelse[0], N.If):
out += f'\n{" " * self._Depth}elif {self._expr(node.orelse[0].test)}:\n{self._Block(node.orelse[0].body)}'
out += self._IfTail(node.orelse[0])
else:
out += f'\n{" " * self._Depth}else:\n{self._Block(node.orelse)}'
return out
def _With(self, node: N.With) -> str:
kw: str = 'async with' if isinstance(node, N.AsyncWith) else 'with'
items: list[str] = []
for item in node.items:
s: str = self._expr(item.context_expr)
if item.optional_vars is not None:
s += f' as {self._expr(item.optional_vars)}'
items.append(s)
return f'{kw} {", ".join(items)}:\n{self._Block(node.body)}'
def _Raise(self, node: N.Raise) -> str:
if node.exc is None:
return 'raise'
out: str = f'raise {self._expr(node.exc)}'
if node.cause is not None:
out += f' from {self._expr(node.cause)}'
return out
def _Try(self, node: N.Try) -> str:
out: str = f'try:\n{self._Block(node.body)}'
for h in node.handlers:
hdr: str = 'except*' if isinstance(node, N.TryStar) else 'except'
if h.type is not None:
hdr += f' {self._expr(h.type)}'
if h.name:
hdr += f' as {h.name}'
out += f'\n{" " * self._Depth}{hdr}:\n{self._Block(h.body)}'
if node.orelse:
out += f'\n{" " * self._Depth}else:\n{self._Block(node.orelse)}'
if node.finalbody:
out += f'\n{" " * self._Depth}finally:\n{self._Block(node.finalbody)}'
return out
def _Assert(self, node: N.Assert) -> str:
out: str = f'assert {self._expr(node.test)}'
if node.msg is not None:
out += f', {self._expr(node.msg)}'
return out
def _Import(self, node: N.Import) -> str:
names: list[str] = []
for a in node.names:
s: str = a.name
if a.asname:
s += f' as {a.asname}'
names.append(s)
return 'import ' + ', '.join(names)
def _ImportFrom(self, node: N.ImportFrom) -> str:
dots: str = '.' * node.level
mod: str = node.module or ''
names: list[str] = []
for a in node.names:
s: str = a.name
if a.asname:
s += f' as {a.asname}'
names.append(s)
return f'from {dots}{mod} import {", ".join(names)}'
def _Global(self, node: N.AST) -> str:
kw: str = 'global' if isinstance(node, N.Global) else 'nonlocal'
return f'{kw} {", ".join(node.names)}'
def _ExprStmt(self, node: N.Expr) -> str:
return self._expr(node.value)
def _Pass(self, node: N.Pass) -> str:
return 'pass'
def _Match(self, node: N.Match) -> str:
# switch 语法糖 desugar 为 is_switch=True 的 Match:还原时用 switch +
# `default:`(而非 match + `case _:`),保证 round-trip 忠实。
Head: str = 'switch' if node.is_switch else 'match'
out: str = f'{Head} {self._expr(node.subject)}:\n'
self._Depth += 1
pad: str = ' ' * self._Depth
for case in node.cases:
if node.is_switch and isinstance(case.pattern, N.MatchAs) and case.pattern.pattern is None:
out += f'{pad}default:\n'
else:
guard: str = f' if {self._expr(case.guard)}' if case.guard else ''
out += f'{pad}case {self._Pattern(case.pattern)}{guard}:\n'
self._Depth += 1
out += self._Block(case.body)
self._Depth -= 1
self._Depth -= 1
return out
def _Pattern(self, node: N.pattern) -> str:
name: str = type(node).__name__
if isinstance(node, N.MatchValue):
return self._expr(node.value)
if isinstance(node, N.MatchSingleton):
return repr(node.value)
if isinstance(node, N.MatchSequence):
return '[' + ', '.join(self._Pattern(p) for p in node.patterns) + ']'
if isinstance(node, N.MatchMapping):
parts: list[str] = []
for k, p in zip(node.keys, node.patterns):
parts.append(f'{self._expr(k)}: {self._Pattern(p)}')
if node.rest:
parts.append(f'**{node.rest}')
return '{' + ', '.join(parts) + '}'
if isinstance(node, N.MatchClass):
pos: list[str] = [self._Pattern(p) for p in node.patterns]
kw: list[str] = [f'{a}={self._Pattern(p)}' for a, p in zip(node.kwd_attrs, node.kwd_patterns)]
return f'{self._expr(node.cls)}({", ".join(pos + kw)})'
if isinstance(node, N.MatchStar):
return f'*{node.name}' if node.name else '*_'
if isinstance(node, N.MatchAs):
if node.pattern is not None:
return f'{self._Pattern(node.pattern)} as {node.name}'
return node.name or '_'
if isinstance(node, N.MatchOr):
return ' | '.join(self._Pattern(p) for p in node.patterns)
raise NotImplementedError(f'unparse pattern: {name}')
# ------------------------------------------------------------------
# 表达式
# ------------------------------------------------------------------
def _expr(self, node: N.AST) -> str:
name: str = type(node).__name__
m: dict[str, object] = {
'Name': self._Name,
'Constant': self._Constant,
'Attribute': self._Attribute,
'Call': self._Call,
'BinOp': self._BinOp,
'UnaryOp': self._UnaryOp,
'BoolOp': self._BoolOp,
'Compare': self._Compare,
'Subscript': self._Subscript,
'Slice': self._Slice,
'Tuple': self._Tuple,
'List': self._List,
'Set': self._Set,
'Dict': self._Dict,
'JoinedStr': self._JoinedStr,
'FormattedValue': self._FormattedValue,
'Lambda': self._Lambda,
'IfExp': self._IfExp,
'Starred': self._Starred,
'ListComp': self._ListComp,
'SetComp': self._ListComp,
'GeneratorExp': self._ListComp,
'DictComp': self._DictComp,
'NamedExpr': self._NamedExpr,
'Await': self._Await,
'Yield': self._Yield,
'YieldFrom': self._YieldFrom,
'New': self._New,
}
fn = m.get(name)
if fn is not None:
return fn(node) # type: ignore[operator]
raise NotImplementedError(f'unparse expr: {name}')
def _New(self, node: N.New) -> str:
"""`new [hlife|slife] [stack|heap|memgr(x)] T(args)`(与 ExprParser 的取值同口径:
NEW_SRC_STACK=1/HEAP=2/MEMGR=3,NEW_LIFE_SLIFE=1/HLIFE=2,DEFAULT=0)。"""
out: str = 'new'
if getattr(node, 'Life', 0) == 1:
out += ' slife'
elif getattr(node, 'Life', 0) == 2:
out += ' hlife'
src: int = getattr(node, 'Source', 0)
if src == 1:
out += ' stack'
elif src == 2:
out += ' heap'
elif src == 3 and node.memgr is not None:
out += f' memgr({self._expr(node.memgr)})'
callee: str = self._expr(node.func) if node.func is not None else ''
if node.args:
callee += '(' + ', '.join(self._expr(a) for a in node.args) + ')'
return f'{out} {callee}'
def _Name(self, node: N.Name) -> str:
return self._WithTypeModifiers(node, node.id)
def _Constant(self, node: N.Constant) -> str:
v = node.value
if v is Ellipsis:
text = '...'
elif v is None:
text = 'None'
elif v is True:
text = 'True'
elif v is False:
text = 'False'
else:
text = repr(v)
return self._WithTypeModifiers(node, text)
def _Attribute(self, node: N.Attribute) -> str:
return self._WithTypeModifiers(node, f'{self._expr(node.value)}.{node.attr}')
def _Call(self, node: N.Call) -> str:
args: list[str] = [self._expr(a) for a in node.args]
for kw in node.keywords:
if kw.arg is None:
args.append(f'**{self._expr(kw.value)}')
else:
args.append(f'{kw.arg}={self._expr(kw.value)}')
return self._WithTypeModifiers(node, f'{self._expr(node.func)}({", ".join(args)})')
_BINOP_OPS: dict[type, str] = {
N.Add: '+', N.Sub: '-', N.Mult: '*', N.Div: '/', N.FloorDiv: '//',
N.Mod: '%', N.Pow: '**', N.LShift: '<<', N.RShift: '>>',
N.BitOr: '|', N.BitXor: '^', N.BitAnd: '&', N.MatMult: '@',
}
def _BinOp(self, node: N.BinOp) -> str:
op: str = self._BINOP_OPS.get(type(node.op), '?')
return f'{self._expr(node.left)} {op} {self._expr(node.right)}'
def _UnaryOp(self, node: N.UnaryOp) -> str:
ops: dict[type, str] = {N.USub: '-', N.UAdd: '+', N.Not: 'not ', N.Invert: '~',
N.Deref: '*', N.AddrOf: '&'}
op: str = ops.get(type(node.op), '?')
return f'{op}{self._expr(node.operand)}'
def _BoolOp(self, node: N.BoolOp) -> str:
kw: str = 'or' if isinstance(node.op, N.Or) else 'and'
return f' {kw} '.join(self._expr(v) for v in node.values)
_CMPOPS: dict[type, str] = {
N.Eq: '==', N.NotEq: '!=', N.Lt: '<', N.LtE: '<=', N.Gt: '>', N.GtE: '>=',
N.Is: 'is', N.IsNot: 'is not', N.In: 'in', N.NotIn: 'not in',
}
def _Compare(self, node: N.Compare) -> str:
parts: list[str] = [self._expr(node.left)]
for op, comp in zip(node.ops, node.comparators):
parts.append(self._CMPOPS.get(type(op), '?'))
parts.append(self._expr(comp))
return ' '.join(parts)
def _Subscript(self, node: N.Subscript) -> str:
return self._WithTypeModifiers(
node, f'{self._expr(node.value)}[{self._SliceLike(node.slice)}]')
def _SliceLike(self, node: N.AST) -> str:
if isinstance(node, N.Slice):
return self._Slice(node)
if isinstance(node, N.Tuple):
# 切片元组:a[1, 2] / a[i, j:k] 元素按需渲染
return ', '.join(self._SliceLike(e) for e in node.elts)
return self._expr(node)
def _Slice(self, node: N.Slice) -> str:
"""渲染切片:a[1:2] / a[::2] / a[:2] / a[1:2:3]。"""
parts: list[str] = []
if node.lower is not None:
parts.append(self._expr(node.lower))
# 冒号:upper 或 step 存在时才需要第一个冒号
if node.upper is not None or node.step is not None:
if len(parts) < 1:
parts.append('')
parts.append(self._expr(node.upper) if node.upper is not None else '')
if node.step is not None:
if len(parts) < 2:
parts.append('')
parts.append('')
parts.append(self._expr(node.step))
return ':'.join(parts)
def _Tuple(self, node: N.Tuple) -> str:
if len(node.elts) == 1:
return f'({self._expr(node.elts[0])},)'
return '(' + ', '.join(self._expr(e) for e in node.elts) + ')'
def _List(self, node: N.List) -> str:
return '[' + ', '.join(self._expr(e) for e in node.elts) + ']'
def _Set(self, node: N.Set) -> str:
return '{' + ', '.join(self._expr(e) for e in node.elts) + '}'
def _Dict(self, node: N.Dict) -> str:
parts: list[str] = []
for k, v in zip(node.keys, node.values):
if k is None:
parts.append(f'**{self._expr(v)}')
else:
parts.append(f'{self._expr(k)}: {self._expr(v)}')
return '{' + ', '.join(parts) + '}'
def _JoinedStr(self, node: N.JoinedStr) -> str:
out: list[str] = []
for v in node.values:
if isinstance(v, N.Constant):
# 字面量段:转义内部引号与花括号
lit: str = str(v.value)
lit = lit.replace('{', '{{').replace('}', '}}')
lit = lit.replace("'", "\\'")
out.append(lit)
else:
out.append('{' + self._FormattedValueRaw(v) + '}')
inner: str = ''.join(out)
# 优先单引号(与 CPython unparse 一致)
return "f'" + inner + "'"
def _FormattedValue(self, node: N.FormattedValue) -> str:
return "f'{" + self._FormattedValueRaw(node) + "}'"
def _FormattedValueRaw(self, node: N.FormattedValue) -> str:
s: str = self._expr(node.value)
if node.conversion >= 0:
s += f'!{chr(node.conversion)}'
if node.format_spec is not None:
if isinstance(node.format_spec, N.JoinedStr):
spec: str = ''
for v in node.format_spec.values:
if isinstance(v, N.Constant):
spec += str(v.value)
else:
spec += '{' + self._FormattedValueRaw(v) + '}'
else:
spec = self._expr(node.format_spec)
s += f':{spec}'
return s
def _Lambda(self, node: N.Lambda) -> str:
return f'lambda {self._Arguments(node.args)}: {self._expr(node.body)}'
def _IfExp(self, node: N.IfExp) -> str:
return f'{self._expr(node.body)} if {self._expr(node.test)} else {self._expr(node.orelse)}'
def _Starred(self, node: N.Starred) -> str:
return f'*{self._expr(node.value)}'
def _ComprehensionClauses(self, gens: list[N.comprehension]) -> str:
out: list[str] = []
for g in gens:
kw: str = 'async for' if g.is_async else 'for'
s: str = f' {kw} {self._expr(g.target)} in {self._expr(g.iter)}'
for cond in g.ifs:
s += f' if {self._expr(cond)}'
out.append(s)
return ''.join(out)
def _ListComp(self, node: N.AST) -> str:
cls_name: str = type(node).__name__
if isinstance(node, N.SetComp):
fmt: str = '{'
close: str = '}'
elt: str = self._expr(node.elt)
elif isinstance(node, N.GeneratorExp):
fmt, close, elt = '(', ')', self._expr(node.elt)
elif isinstance(node, N.DictComp):
return '{' + f'{self._expr(node.key)}: {self._expr(node.value)}' + self._ComprehensionClauses(node.generators) + '}'
else:
fmt, close, elt = '[', ']', self._expr(node.elt)
return fmt + elt + self._ComprehensionClauses(node.generators) + close
def _DictComp(self, node: N.DictComp) -> str:
return '{' + f'{self._expr(node.key)}: {self._expr(node.value)}' + \
self._ComprehensionClauses(node.generators) + '}'
def _NamedExpr(self, node: N.NamedExpr) -> str:
return f'{self._expr(node.target)} := {self._expr(node.value)}'
def _Await(self, node: N.Await) -> str:
return f'await {self._expr(node.value)}'
def _Yield(self, node: N.Yield) -> str:
return 'yield' if node.value is None else f'yield {self._expr(node.value)}'
def _YieldFrom(self, node: N.YieldFrom) -> str:
return f'yield from {self._expr(node.value)}'
# ------------------------------------------------------------------
# 参数列表
# ------------------------------------------------------------------
def _Arguments(self, args: N.arguments) -> str:
parts: list[str] = []
n_args: int = len(args.posonlyargs) + len(args.args)
n_defaults: int = len(args.defaults)
# 合并 posonly + 普通参数,defaults 尾部对齐
all_args: list[N.arg] = list(args.posonlyargs) + list(args.args)
all_defaults: list[N.expr | None] = [None] * (n_args - n_defaults) + list(args.defaults)
for i, a in enumerate(all_args):
s: str = a.arg
if a.annotation is not None:
s += f': {self._expr(a.annotation)}'
if all_defaults[i] is not None:
s += f'={self._expr(all_defaults[i])}'
parts.append(s)
if args.posonlyargs and i == len(args.posonlyargs) - 1:
parts.append('/')
if args.vararg is not None:
s = f'*{args.vararg.arg}'
if args.vararg.annotation is not None:
s += f': {self._expr(args.vararg.annotation)}'
parts.append(s)
elif args.kwonlyargs:
parts.append('*')
for a, d in zip(args.kwonlyargs, args.kw_defaults):
s = a.arg
if a.annotation is not None:
s += f': {self._expr(a.annotation)}'
if d is not None:
s += f'={self._expr(d)}'
parts.append(s)
if args.kwarg is not None:
s = f'**{args.kwarg.arg}'
if args.kwarg.annotation is not None:
s += f': {self._expr(args.kwarg.annotation)}'
parts.append(s)
return ', '.join(parts)
def Unparse(node: N.AST) -> str:
"""便捷入口。"""
return Unparser().Unparse(node)