fix(switch): case 标签支持裸名(按值引用)并给出常量标签 fail-fast

- 裸名在 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 标签必须是编译期常量”
This commit is contained in:
TermiNexus committed 2026-10-03 00:14:51 +08:00
1 parent 61df58334f
commit d6eefcbece
3 files changed
+90

No files matched your search

+10
View File
@@ -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:
+22
View File
@@ -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
+58
View File
@@ -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 <expr>:
@@ -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");