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:
1 parent
61df58334f
commit
d6eefcbece
3 files changed
+90
No files matched your search
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
|
||||
Reference in new issue
Block a user