feat(match): 支持 match/switch 守卫(case X if cond)
通配计数只统计无守卫的 case _,允许多个带守卫的 case _ if ...;_handle_simple_match 预扫描 has_guard,有守卫时改走 _handle_guarded_simple_match(按书写顺序的 if-else 比较链,guard 归一为 i1 后与值比较取与,链尾落 default/end),无守卫时保持原 switch 分派路径逐字不变(零回归);_handle_renum_match 对带守卫臂 fail-fast,避免 switch(tag) 静默忽略 guard。
This commit is contained in:
1 parent
67667b10db
commit
19d7a90c28
1 file changed
+325
-3
@@ -93,7 +93,9 @@ novirtual class MatchGenMixin:
|
||||
# 重名 case 检测(fail-fast):**match 不允许重复 case 标签**(重复标签让
|
||||
# 某个臂永远不可达,且常意味着漏改的分支);**switch 允许重复标签**(按书写
|
||||
# 顺序,先出现者生效),故仅对 match 做值标签去重。
|
||||
# 通配臂(match 的 `case _` / switch 的 `default`)两者都只允许一个。
|
||||
# 通配臂(match 的 `case _` / switch 的 `default`):**无 guard** 的通配臂
|
||||
# 两者都只允许一个(真正的 default);**带 guard** 的通配臂(`case _ if ...`)
|
||||
# 允许多个(唯一性由各自的 guard 区分,见 _handle_guarded_simple_match)。
|
||||
# 在任何 IR 产出之前检查,保证报错即终止。报文前缀按 is_switch 区分。
|
||||
kind_label: str = "match"
|
||||
default_label: str = "`case _`"
|
||||
@@ -112,7 +114,10 @@ novirtual class MatchGenMixin:
|
||||
if dpat is not None:
|
||||
if dpat.kind() == ast.ASTKind.MatchAs \
|
||||
and rawptr[ast.MatchAs](dpat).pattern is None:
|
||||
default_cnt = default_cnt + 1
|
||||
# 只统计**无 guard** 的通配臂:带 guard 的 `case _ if ...`
|
||||
# 允许多个(各自 guard 区分),不当 default 计
|
||||
if rawptr[ast.MatchCase](dcase).guard is None:
|
||||
default_cnt = default_cnt + 1
|
||||
elif m_node.is_switch == 0:
|
||||
dcj: int = dci + 1
|
||||
while dcj < ccn:
|
||||
@@ -127,7 +132,7 @@ novirtual class MatchGenMixin:
|
||||
dci = dci + 1
|
||||
if default_cnt > 1:
|
||||
self.Gen.TypeGenErr.fatal_error(node,
|
||||
f"{kind_label} 出现 {default_cnt} 个 {default_label}(通配/default 只允许一个)", "SyntaxError")
|
||||
f"{kind_label} 出现 {default_cnt} 个{default_label}(无 guard 的通配/default 只允许一个;带 guard 的 `case _ if ...` / `default if ...` 可有多个)", "SyntaxError")
|
||||
|
||||
# 1. 求值 subject
|
||||
subject_node: ast.AST* = m_node.subject
|
||||
@@ -375,6 +380,22 @@ novirtual class MatchGenMixin:
|
||||
# 函数里会使本函数引用退化为隐式全局,跨 match 污染)。
|
||||
rm_merge_reach: int = 0
|
||||
|
||||
# guard 守卫(match/switch 分支条件守卫):本路径用 switch(tag) 一次性分发,
|
||||
# **不评估 guard**——带 guard 的臂会被静默忽略(可能选错分支)。禁止静默兜底:
|
||||
# 一旦存在带 guard 的臂,在任何 IR 产出之前 fail-fast 报错。
|
||||
rn_cases: list[ast.AST*]* = node.cases
|
||||
if rn_cases is not None:
|
||||
rn_n: sizet = len(rn_cases)
|
||||
rn_i: int = 0
|
||||
while rn_i < rn_n:
|
||||
rn_case: ast.AST* = rn_cases.get(rn_i)
|
||||
if rn_case is not None and rn_case.kind() == ast.ASTKind.MatchCase:
|
||||
if rawptr[ast.MatchCase](rn_case).guard is not None:
|
||||
self.Gen.TypeGenErr.fatal_error(rn_case,
|
||||
"REnum match 暂不支持 guard 守卫(该臂会被 switch(tag) 静默忽略);请改为普通 match 或去掉 guard",
|
||||
"NotImplementedError")
|
||||
rn_i = rn_i + 1
|
||||
|
||||
# 0. subject 归一化:确保 subject_ptr 是指向 REnum 结构体的指针
|
||||
# (对齐 TPC HandlesMatch.py:238-259 的 _HandleRenumMatchLlvm)
|
||||
# 情况1: subject_ptr 是 T**(变量 alloca)→ load 一次得 T*
|
||||
@@ -1021,6 +1042,13 @@ novirtual class MatchGenMixin:
|
||||
if pk == ast.ASTKind.MatchAs:
|
||||
ma0: rawptr[ast.MatchAs] = rawptr[ast.MatchAs](pat)
|
||||
if ma0.pattern is None:
|
||||
# 顶层 _ 通配臂带 guard(`case _ if <cond>`):本序列路径把
|
||||
# `_` 一律当 default(恒真)处理,会**静默忽略 guard**。禁止
|
||||
# 静默兜底 → 在任何 IR 产出之前 fail-fast。
|
||||
if mc.guard is not None:
|
||||
self.Gen.TypeGenErr.fatal_error(pat,
|
||||
"序列匹配的顶层 `case _ if <cond>` 暂不支持 guard 守卫",
|
||||
"NotImplementedError")
|
||||
is_def = 1
|
||||
elif pk != ast.ASTKind.MatchSequence:
|
||||
# fail-fast:序列匹配只接受 MatchSequence / 顶层 _ 通配
|
||||
@@ -1272,6 +1300,24 @@ novirtual class MatchGenMixin:
|
||||
# fail-fast: subject 转 i32 失败
|
||||
self.Gen.TypeGenErr.fatal_error(node, "match subject 转换为 i32 失败", "TypeError")
|
||||
|
||||
# 1b. guard 预扫描:任一臂带 guard → 改走按书写顺序的 if-else 比较链
|
||||
# (Python match 语义:按序取首个"模式匹配且 guard 为真"的臂,见
|
||||
# _handle_guarded_simple_match)。**无 guard 时**保持下方既有 switch 分发
|
||||
# 路径逐字不变(不得有任何行为变化)。
|
||||
cases_g: list[ast.AST*]* = node.cases
|
||||
has_guard: int = 0
|
||||
if cases_g is not None:
|
||||
cg_n: sizet = len(cases_g)
|
||||
cg_i: int = 0
|
||||
while cg_i < cg_n:
|
||||
cg_case: ast.AST* = cases_g.get(cg_i)
|
||||
if cg_case is not None and cg_case.kind() == ast.ASTKind.MatchCase:
|
||||
if rawptr[ast.MatchCase](cg_case).guard is not None:
|
||||
has_guard = 1
|
||||
cg_i = cg_i + 1
|
||||
if has_guard != 0:
|
||||
return self._handle_guarded_simple_match(trans, node, subj_val)
|
||||
|
||||
# 2. 创建 default/end BB
|
||||
cnt: int = trans._label_counter
|
||||
trans._label_counter = cnt + 1
|
||||
@@ -1507,3 +1553,279 @@ novirtual class MatchGenMixin:
|
||||
# 覆盖嵌套控制流污染(同主路径)
|
||||
trans._ctrl_merge_reachable = sm_merge_reach
|
||||
return 0
|
||||
|
||||
# ============================================================
|
||||
# _handle_guarded_simple_match - _handle_simple_match 的 guard 分支
|
||||
#
|
||||
# 任一臂带 guard 时改走**按书写顺序**的 if-else 比较链分发(对齐 Python
|
||||
# match 语义:按序取首个"模式匹配且 guard 为真"的臂):
|
||||
# entry → check0
|
||||
# check_i: cond_i → body_i(不满足 → check_{i+1})
|
||||
# body_i : <body> → end(fallthrough 时落到下一个非通配 body)
|
||||
# 全部 check 不中 → default_bb(唯一的**无 guard** 通配臂 body,若有)→ end
|
||||
# cond_i = 值臂(subj_val == case值) / 通配臂(恒真),再与 guard(归一为 i1)取与。
|
||||
# guard 归一化、i1 判定、const_int 参数顺序(pool, bits, val)均与
|
||||
# _handle_sequence_match 同款(L1118-1135)。
|
||||
# 注意:**无 guard** 的 match/switch 不会进入本函数(见 _handle_simple_match
|
||||
# 的 has_guard 预扫描),既有 switch 分派路径逐字不变。
|
||||
# ============================================================
|
||||
def _handle_guarded_simple_match(self, trans: HT.Translator*,
|
||||
node: ast.Match*,
|
||||
subj_val: llvmlite.Value*) -> int:
|
||||
pool: memhub.MemBuddy* = trans.Pool
|
||||
builder: llvmlite.IRBuilder* = trans._cur_builder
|
||||
func: llvmlite.Function* = trans._cur_func
|
||||
# 同主路径:局部记录本 guarded match 的 br end_bb,收尾覆盖
|
||||
# _ctrl_merge_reachable,消除 case 体内嵌套控制流污染
|
||||
gm_merge_reach: int = 0
|
||||
|
||||
# 1. 创建 default/end BB
|
||||
cnt: int = trans._label_counter
|
||||
trans._label_counter = cnt + 1
|
||||
name_buf: str = pool.alloc(32)
|
||||
if name_buf is None:
|
||||
self.Gen.TypeGenErr.fatal_error(node, "标签缓冲区分配失败", "TypeError")
|
||||
viperlib.snprintf(name_buf, 32, "match.guard.default.%d", cnt)
|
||||
default_bb: rawptr[llvmlite.BasicBlock] = llvmlite.create_block(pool, func, name_buf)
|
||||
viperlib.snprintf(name_buf, 32, "match.guard.end.%d", cnt)
|
||||
end_bb: rawptr[llvmlite.BasicBlock] = llvmlite.create_block(pool, func, name_buf)
|
||||
|
||||
# 2. 收集 case 信息(check/body BB、default 标记、nobreak、mc、cv)
|
||||
cases_list: list[ast.AST*]* = node.cases
|
||||
if cases_list is None:
|
||||
self.Gen.TypeGenErr.fatal_error(node, "match cases 缺失", "TypeError")
|
||||
case_count: sizet = len(cases_list)
|
||||
check_arr: sizet* = pool.alloc(8 * MAX_MATCH_CASES)
|
||||
body_arr: sizet* = pool.alloc(8 * MAX_MATCH_CASES)
|
||||
def_arr: int | t.CPtr = pool.alloc(4 * MAX_MATCH_CASES)
|
||||
nb_arr: int | t.CPtr = pool.alloc(4 * MAX_MATCH_CASES)
|
||||
mc_arr: sizet* = pool.alloc(8 * MAX_MATCH_CASES)
|
||||
cv_arr: sizet* = pool.alloc(8 * MAX_MATCH_CASES)
|
||||
if check_arr is None or body_arr is None or def_arr is None \
|
||||
or nb_arr is None or mc_arr is None or cv_arr is None:
|
||||
self.Gen.TypeGenErr.fatal_error(node, "match 分派数组分配失败", "TypeError")
|
||||
collected: int = 0
|
||||
has_default: int = 0
|
||||
# 唯一的**无 guard** 通配臂(default):其 body = default_bb,且 default_bb
|
||||
# 是所有 check 不中时的落点。带 guard 的通配臂不算 default(各有独立
|
||||
# check/body BB),避免其 guard 被静默忽略。
|
||||
default_case_node: ast.AST* = None
|
||||
first_check: int = -1
|
||||
i: int = 0
|
||||
while i < case_count:
|
||||
if collected >= MAX_MATCH_CASES:
|
||||
self.Gen.TypeGenErr.fatal_error(node, f"match case 数量超过上限 MAX_MATCH_CASES={MAX_MATCH_CASES}", "OverflowError")
|
||||
break
|
||||
case_node: ast.AST* = cases_list.get(i)
|
||||
if case_node is None or case_node.kind() != ast.ASTKind.MatchCase:
|
||||
i = i + 1
|
||||
continue
|
||||
mc: rawptr[ast.MatchCase] = rawptr[ast.MatchCase](case_node)
|
||||
pat: ast.AST* = mc.pattern
|
||||
gnode0: ast.AST* = mc.guard
|
||||
is_def: int = 0
|
||||
cv: llvmlite.Value* = None
|
||||
if pat is not None:
|
||||
pk: int = pat.kind()
|
||||
if pk == ast.ASTKind.MatchValue:
|
||||
mv: rawptr[ast.MatchValue] = rawptr[ast.MatchValue](pat)
|
||||
cv = self.Gen.ExprGen.translate_value(builder, pool, trans.Module, mv.value,
|
||||
trans._funcs, trans._func_count, trans)
|
||||
elif pk == ast.ASTKind.MatchSingleton:
|
||||
# None -> 0
|
||||
cv = llvmlite.const_int32(pool, 0)
|
||||
elif pk == ast.ASTKind.MatchAs:
|
||||
ma: rawptr[ast.MatchAs] = rawptr[ast.MatchAs](pat)
|
||||
if ma.pattern is None and gnode0 is None:
|
||||
# 唯一的**无 guard** 通配臂 → default
|
||||
is_def = 1
|
||||
# ma.pattern is None 且 guard 非空 → 带 guard 的通配臂:条件恒真
|
||||
# 再与 guard 取与(见发射阶段),不当作 default
|
||||
elif pk == ast.ASTKind.MatchOr:
|
||||
# MatchOr:取第一个 MatchValue 作比较(与 _handle_simple_match 的
|
||||
# switch 路径同口径,其余子模式在 guard 链中不展开)
|
||||
mo: rawptr[ast.MatchOr] = rawptr[ast.MatchOr](pat)
|
||||
pats_o: list[ast.AST*]* = mo.patterns
|
||||
if pats_o is not None and len(pats_o) > 0:
|
||||
fp: ast.AST* = pats_o.get(0)
|
||||
if fp is not None and fp.kind() == ast.ASTKind.MatchValue:
|
||||
mv2: rawptr[ast.MatchValue] = rawptr[ast.MatchValue](fp)
|
||||
cv = self.Gen.ExprGen.translate_value(builder, pool, trans.Module, mv2.value,
|
||||
trans._funcs, trans._func_count, trans)
|
||||
else:
|
||||
# 禁止静默兜底:guard 分支只支持 值/None/通配/或 模式
|
||||
self.Gen.TypeGenErr.fatal_error(pat, "guard 分支不支持的 case 模式", "TypeError")
|
||||
if is_def == 1:
|
||||
has_default = 1
|
||||
default_case_node = case_node
|
||||
check_arr[collected] = 0
|
||||
body_arr[collected] = sizet(default_bb)
|
||||
else:
|
||||
viperlib.snprintf(name_buf, 32, "match.guard.check.%d.%d", cnt, i)
|
||||
cbb: rawptr[llvmlite.BasicBlock] = llvmlite.create_block(pool, func, name_buf)
|
||||
viperlib.snprintf(name_buf, 32, "match.guard.body.%d.%d", cnt, i)
|
||||
bbb: rawptr[llvmlite.BasicBlock] = llvmlite.create_block(pool, func, name_buf)
|
||||
check_arr[collected] = sizet(cbb)
|
||||
body_arr[collected] = sizet(bbb)
|
||||
if first_check < 0:
|
||||
first_check = collected
|
||||
body_list: list[ast.AST*]* = mc.children
|
||||
nb: int = self._has_no_break(body_list)
|
||||
def_arr[collected] = is_def
|
||||
nb_arr[collected] = nb
|
||||
mc_arr[collected] = sizet(mc)
|
||||
cv_arr[collected] = sizet(cv)
|
||||
collected = collected + 1
|
||||
i = i + 1
|
||||
|
||||
# 3. 入口 → 第一个 check(或 default/end)
|
||||
if first_check >= 0:
|
||||
head_bb: rawptr[llvmlite.BasicBlock] = rawptr[llvmlite.BasicBlock](check_arr[first_check])
|
||||
llvmlite.build_br(builder, head_bb)
|
||||
elif has_default == 1:
|
||||
llvmlite.build_br(builder, default_bb)
|
||||
else:
|
||||
llvmlite.build_br(builder, end_bb)
|
||||
|
||||
# 4. 发射 check 链(按书写顺序)
|
||||
ci: int = 0
|
||||
while ci < collected:
|
||||
if def_arr[ci] == 1:
|
||||
ci = ci + 1
|
||||
continue
|
||||
cbb2: rawptr[llvmlite.BasicBlock] = rawptr[llvmlite.BasicBlock](check_arr[ci])
|
||||
bbb2: rawptr[llvmlite.BasicBlock] = rawptr[llvmlite.BasicBlock](body_arr[ci])
|
||||
llvmlite.position_at_end(builder, cbb2)
|
||||
mc2: rawptr[ast.MatchCase] = rawptr[ast.MatchCase](mc_arr[ci])
|
||||
# 值条件:subj_val == case值(通配臂无 case 值 → cond 保持 None=恒真)
|
||||
cond: llvmlite.Value* = None
|
||||
cv_addr: sizet = cv_arr[ci]
|
||||
if cv_addr != 0:
|
||||
cv2: llvmlite.Value* = (llvmlite.Value*)(cv_addr)
|
||||
c1: llvmlite.Value* = self._seq_cmp_eq(trans, builder, subj_val, cv2)
|
||||
if c1 is None:
|
||||
self.Gen.TypeGenErr.fatal_error(node, "match case 值比较失败", "TypeError")
|
||||
cond = c1
|
||||
# guard(若有):匹配成立且 guard 非零(归一为 i1,写法同序列路径)
|
||||
g_addr: sizet = sizet(mc2.guard)
|
||||
if g_addr != 0:
|
||||
gnode: ast.AST* = (ast.AST*)(g_addr)
|
||||
gv: llvmlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, trans.Module, gnode,
|
||||
trans._funcs, trans._func_count, trans)
|
||||
if gv is None:
|
||||
self.Gen.TypeGenErr.fatal_error(gnode, "match guard 翻译失败", "TypeError")
|
||||
gbits: int = self.Gen.ExprGenBase.get_llvm_type_bits(gv.Ty)
|
||||
if gbits == 1:
|
||||
g1: llvmlite.Value* = gv
|
||||
else:
|
||||
gzero: llvmlite.Value* = llvmlite.const_int(pool, gbits if gbits > 0 else 32, 0)
|
||||
g1 = llvmlite.build_icmp(builder, llvmlite.ICMP_NE, gv, gzero)
|
||||
if cond is None:
|
||||
cond = g1
|
||||
else:
|
||||
cond = llvmlite.build_and(builder, cond, g1)
|
||||
if cond is None:
|
||||
cond = llvmlite.const_int(pool, 1, 1)
|
||||
# 下一个 check(跳过 default)→ 否则 default/end
|
||||
nxt: rawptr[llvmlite.BasicBlock] = None
|
||||
nj: int = ci + 1
|
||||
while nj < collected and def_arr[nj] == 1:
|
||||
nj = nj + 1
|
||||
if nj < collected:
|
||||
nxt = rawptr[llvmlite.BasicBlock](check_arr[nj])
|
||||
elif has_default == 1:
|
||||
nxt = default_bb
|
||||
else:
|
||||
nxt = end_bb
|
||||
llvmlite.build_cond_br(builder, cond, bbb2, nxt)
|
||||
ci = ci + 1
|
||||
|
||||
# 5. 翻译 case body
|
||||
ci = 0
|
||||
while ci < collected:
|
||||
if def_arr[ci] == 1:
|
||||
ci = ci + 1
|
||||
continue
|
||||
bbb3: rawptr[llvmlite.BasicBlock] = rawptr[llvmlite.BasicBlock](body_arr[ci])
|
||||
llvmlite.position_at_end(builder, bbb3)
|
||||
mc3: rawptr[ast.MatchCase] = rawptr[ast.MatchCase](mc_arr[ci])
|
||||
# case 体内 break = 跳出 match(C switch 语义):暂存/覆盖 _break_bb
|
||||
gmb_old_break: rawptr[llvmlite.BasicBlock] = trans._break_bb
|
||||
trans._break_bb = end_bb
|
||||
gmb_old_inmc: int = trans._in_switch_case
|
||||
trans._in_switch_case = 1
|
||||
self.Gen.VarGen.enter_scope(trans.SymTab, SCOPE_MATCH)
|
||||
body_list2: list[ast.AST*]* = mc3.children
|
||||
if body_list2 is not None:
|
||||
bc: sizet = len(body_list2)
|
||||
bi: int = 0
|
||||
while bi < bc:
|
||||
stmt: ast.AST* = body_list2.get(bi)
|
||||
if stmt is not None:
|
||||
self.Gen.StmtGen.translate_stmt(trans, stmt)
|
||||
bi = bi + 1
|
||||
self.Gen.VarGen.exit_scope(trans.SymTab)
|
||||
trans._break_bb = gmb_old_break
|
||||
trans._in_switch_case = gmb_old_inmc
|
||||
# 若未终止:默认跳 end;含 fallthrough 则落到下一个非通配 body。
|
||||
# 末句控制流全 return → 补 unreachable(不 br end_bb)
|
||||
if llvmlite.builder_cur_block_is_terminated(builder) == 0 \
|
||||
and self._case_tail_unreachable(trans, builder, body_list2) == 0:
|
||||
if nb_arr[ci] == 0:
|
||||
llvmlite.build_br(builder, end_bb)
|
||||
else:
|
||||
nj2: int = ci + 1
|
||||
while nj2 < collected and def_arr[nj2] == 1:
|
||||
nj2 = nj2 + 1
|
||||
if nj2 < collected:
|
||||
nbb: rawptr[llvmlite.BasicBlock] = rawptr[llvmlite.BasicBlock](body_arr[nj2])
|
||||
llvmlite.build_br(builder, nbb)
|
||||
elif has_default == 1:
|
||||
llvmlite.build_br(builder, default_bb)
|
||||
else:
|
||||
llvmlite.build_br(builder, end_bb)
|
||||
trans._ctrl_merge_reachable = 1
|
||||
gm_merge_reach = 1
|
||||
ci = ci + 1
|
||||
|
||||
# 6. default 块(唯一的**无 guard** 通配臂;全部 check 不中时落此)
|
||||
llvmlite.position_at_end(builder, default_bb)
|
||||
if has_default == 0 or default_case_node is None:
|
||||
# 无无 guard 通配臂:match 非穷尽 ⇒ merge 可达,default 直接跳 end
|
||||
if llvmlite.builder_cur_block_is_terminated(builder) == 0:
|
||||
llvmlite.build_br(builder, end_bb)
|
||||
trans._ctrl_merge_reachable = 1
|
||||
gm_merge_reach = 1
|
||||
else:
|
||||
dmc_d: rawptr[ast.MatchCase] = rawptr[ast.MatchCase](default_case_node)
|
||||
# default 臂同样按 match-case 语义:break = 跳出 match
|
||||
gmd_old_break: rawptr[llvmlite.BasicBlock] = trans._break_bb
|
||||
trans._break_bb = end_bb
|
||||
gmd_old_inmc: int = trans._in_switch_case
|
||||
trans._in_switch_case = node.is_switch
|
||||
self.Gen.VarGen.enter_scope(trans.SymTab, SCOPE_MATCH)
|
||||
# dbody3 内联声明接收者遍历(规避 .get 误分派成 list[int].get 的截断)
|
||||
dbody3: list[ast.AST*]* = dmc_d.children
|
||||
if dbody3 is not None:
|
||||
dn3: sizet = len(dbody3)
|
||||
dj3: int = 0
|
||||
while dj3 < dn3:
|
||||
stmt3: ast.AST* = dbody3.get(dj3)
|
||||
if stmt3 is not None:
|
||||
self.Gen.StmtGen.translate_stmt(trans, stmt3)
|
||||
dj3 = dj3 + 1
|
||||
self.Gen.VarGen.exit_scope(trans.SymTab)
|
||||
trans._break_bb = gmd_old_break
|
||||
trans._in_switch_case = gmd_old_inmc
|
||||
# 末句控制流全 return → 补 unreachable(不 br end_bb)
|
||||
if llvmlite.builder_cur_block_is_terminated(builder) == 0 \
|
||||
and self._case_tail_unreachable(trans, builder, dbody3) == 0:
|
||||
llvmlite.build_br(builder, end_bb)
|
||||
trans._ctrl_merge_reachable = 1
|
||||
gm_merge_reach = 1
|
||||
|
||||
# 7. 定位到 end
|
||||
llvmlite.position_at_end(builder, end_bb)
|
||||
# 覆盖嵌套控制流污染(同主路径)
|
||||
trans._ctrl_merge_reachable = gm_merge_reach
|
||||
return 0
|
||||
Reference in new issue
Block a user