From d6eefcbece329d2ba75a94a8f50fe2922c9a0402 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Sat, 3 Oct 2026 00:14:51 +0800 Subject: [PATCH] =?UTF-8?q?fix(switch):=20case=20=E6=A0=87=E7=AD=BE?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E8=A3=B8=E5=90=8D=EF=BC=88=E6=8C=89=E5=80=BC?= =?UTF-8?q?=E5=BC=95=E7=94=A8=EF=BC=89=E5=B9=B6=E7=BB=99=E5=87=BA=E5=B8=B8?= =?UTF-8?q?=E9=87=8F=E6=A0=87=E7=AD=BE=20fail-fast?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 裸名在 match 里是捕获模式(MatchAs),switch 里对齐 C 的 `case NAME:` 语义, 三端解析均归一为 MatchValue(Name);`_` 仍作通配(等价 default) - includes/ast/parser.vp、lib/vast/Parser.py、lib/vast_c/vast_c.c 同步实现 - HandlesMatch:case 标签求值非编译期常量时 fail-fast(原先会因取 .constant 崩成 AttributeError),报文明确“case 标签必须是编译期常量” --- lib/core/Handles/HandlesMatch.py | 10 ++++++ lib/vast/Parser.py | 22 ++++++++++++ lib/vast_c/vast_c.c | 58 ++++++++++++++++++++++++++++++++ 3 files changed, 90 insertions(+) diff --git a/lib/core/Handles/HandlesMatch.py b/lib/core/Handles/HandlesMatch.py index 5658863..6c728ef 100644 --- a/lib/core/Handles/HandlesMatch.py +++ b/lib/core/Handles/HandlesMatch.py @@ -171,6 +171,11 @@ class MatchHandle(BaseHandle): if isinstance(pattern, ast.MatchValue): Val: ir.Value | None = self.HandleExprLlvm(pattern.value) if Val: + if not isinstance(Val, ir.Constant): + from lib.core.LLVMCG.FuncGen import FailFastError + raise FailFastError( + "switch/match 的 case 标签必须是编译期常量" + "(当前标签求值成运行期值,无法作为 switch 的 case 常量)") CaseVal: ir.Value if isinstance(Val.type, ir.IntType): if Val.type != SwitchIntType: @@ -192,6 +197,11 @@ class MatchHandle(BaseHandle): if isinstance(SubPattern, ast.MatchValue): Val: ir.Value | None = self.HandleExprLlvm(SubPattern.value) if Val: + if not isinstance(Val, ir.Constant): + from lib.core.LLVMCG.FuncGen import FailFastError + raise FailFastError( + "switch/match 的 case 标签必须是编译期常量" + "(当前标签求值成运行期值,无法作为 switch 的 case 常量)") CaseVal: ir.Value if isinstance(Val.type, ir.IntType): if Val.type != SwitchIntType: diff --git a/lib/vast/Parser.py b/lib/vast/Parser.py index c10176e..25ef138 100644 --- a/lib/vast/Parser.py +++ b/lib/vast/Parser.py @@ -1355,6 +1355,7 @@ class Parser(ExprParserMixin): if self._AtName('case'): case_start: Token = self._Next() # case pattern: N.pattern = self._ParsePattern() + pattern = self._SwitchLabelFromPattern(pattern) self._CheckSwitchCasePattern(pattern) if self._AtName('if'): raise SyntaxError("switch 的 case 不支持 if 守卫") @@ -1381,6 +1382,24 @@ class Parser(ExprParserMixin): self._FixLoc(node, start) return node + @staticmethod + def _SwitchLabelFromPattern(Pattern: N.pattern) -> N.pattern: + """switch 的 case 标签修正:裸名按**值引用**处理 + + match 里裸名是捕获模式(`MatchAs(pattern=None, name=X)`),switch 里应对齐 + C 的 `case NAME:` 语义 —— 归一为 `MatchValue(Name('X'))`;`_` 仍是通配符 + (留作 MatchAs 通配,等价 `default:`)。 + """ + if isinstance(Pattern, N.MatchAs) and Pattern.pattern is None and Pattern.name is not None: + Ref: N.Name = N.Name(id=Pattern.name, ctx=N.Load()) + Val: N.MatchValue = N.MatchValue(value=Ref) + Val.lineno = Pattern.lineno + Val.col_offset = Pattern.col_offset + Val.end_lineno = Pattern.end_lineno + Val.end_col_offset = Pattern.end_col_offset + return Val + return Pattern + def _PeekIsOp(self, value: str) -> bool: """下一个 token 是否为运算符 `value`(不消费)""" nxt: Token = self._Peek(1) @@ -1395,6 +1414,9 @@ class Parser(ExprParserMixin): def _IsAllowed(p: N.pattern) -> bool: if isinstance(p, (N.MatchValue, N.MatchSingleton, N.MatchClass)): return True + # 通配 `case _`(MatchAs 无子模式)等价 default + if isinstance(p, N.MatchAs): + return p.pattern is None if isinstance(p, N.MatchOr): return len(p.patterns) > 0 and all(_IsAllowed(x) for x in p.patterns) return False diff --git a/lib/vast_c/vast_c.c b/lib/vast_c/vast_c.c index 7553a56..7fa8716 100644 --- a/lib/vast_c/vast_c.c +++ b/lib/vast_c/vast_c.c @@ -6456,6 +6456,17 @@ static int SwitchPatAllowed(PyObject *Pat) { || strcmp(N, "MatchClass") == 0) { return 1; } + /* 通配 `case _`(MatchAs 无子模式)等价 default */ + if (strcmp(N, "MatchAs") == 0) { + PyObject *Inner = PyObject_GetAttrString(Pat, "pattern"); + if (Inner == NULL) { + PyErr_Clear(); + return 0; + } + int IsWild = (Inner == Py_None); + Py_DECREF(Inner); + return IsWild; + } if (strcmp(N, "MatchOr") == 0) { PyObject *Pats = PyObject_GetAttrString(Pat, "patterns"); if (Pats == NULL) { @@ -6479,6 +6490,52 @@ static int SwitchPatAllowed(PyObject *Pat) { return 0; } +/* switch 的 case 标签修正:裸名按**值引用**处理 + * + * match 里裸名是捕获模式(MatchAs,pattern=None),switch 里对齐 C 的 `case NAME:` + * 语义 —— 归一为 MatchValue(Name(name, Load));`_`(name=None)仍是通配,原样保留。 */ +static PyObject *SwitchLabelFromPattern(Parser *P, PyObject *Pat) { + if (Pat == NULL || strcmp(Py_TYPE(Pat)->tp_name, "MatchAs") != 0) { + return Pat; + } + PyObject *Inner = PyObject_GetAttrString(Pat, "pattern"); + PyObject *NameStr = PyObject_GetAttrString(Pat, "name"); + if (Inner == NULL || NameStr == NULL) { + PyErr_Clear(); + Py_XDECREF(Inner); + Py_XDECREF(NameStr); + return Pat; + } + int IsBareName = (Inner == Py_None && NameStr != Py_None); + Py_DECREF(Inner); + if (!IsBareName) { + Py_DECREF(NameStr); + return Pat; + } + PyObject *Load = MakeCtx(P, "Load"); + if (Load == NULL) { + Py_DECREF(NameStr); + return Pat; + } + PyObject *NameArgv[2] = { NameStr, Load }; + PyObject *NameNode = NewNode(P, "Name", NameArgv, 2); + Py_DECREF(Load); + if (NameNode == NULL) { + Py_DECREF(NameStr); + return Pat; + } + /* Name 文本已在 NameNode 内自持,可安全释放 */ + Py_DECREF(NameStr); + PyObject *MvArgv[1] = { NameNode }; + PyObject *Mv = NewNode(P, "MatchValue", MvArgv, 1); + Py_DECREF(NameNode); + if (Mv == NULL) { + return Pat; + } + Py_DECREF(Pat); + return Mv; +} + /* switch 语句(C switch 语义):脱糖为 is_switch=True 的 Match * * switch : @@ -6523,6 +6580,7 @@ static PyObject *ParseSwitch(Parser *P) { Py_DECREF(Subject); return NULL; } + Pattern = SwitchLabelFromPattern(P, Pattern); if (!SwitchPatAllowed(Pattern)) { SyntaxErrorAt(P, CaseStart, "switch case label must be a constant or enum variant");