refactor document scope creation to be more aware of block expressions
This commit is contained in:
parent
53c7e5bed7
commit
7e652a5527
296
src/analysis.zig
296
src/analysis.zig
@ -2556,6 +2556,36 @@ fn makeInnerScope(context: ScopeContext, node_idx: Ast.Node.Index) error{OutOfMe
|
|||||||
scopes.items(.uses)[scope_index] = try uses.toOwnedSlice(allocator);
|
scopes.items(.uses)[scope_index] = try uses.toOwnedSlice(allocator);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// If `node_idx` is a block it's scope index will be returned
|
||||||
|
/// Otherwise, a new scope will be created that will enclose `node_idx`
|
||||||
|
fn makeBlockScopeInternal(context: ScopeContext, node_idx: Ast.Node.Index) error{OutOfMemory}!?usize {
|
||||||
|
if (node_idx == 0) return null;
|
||||||
|
const tags = context.tree.nodes.items(.tag);
|
||||||
|
|
||||||
|
// if node_idx is a block, the next scope will be a block so we store its index here
|
||||||
|
const block_scope_index = context.scopes.len;
|
||||||
|
try makeScopeInternal(context, node_idx);
|
||||||
|
|
||||||
|
switch (tags[node_idx]) {
|
||||||
|
.block,
|
||||||
|
.block_semicolon,
|
||||||
|
.block_two,
|
||||||
|
.block_two_semicolon,
|
||||||
|
=> {
|
||||||
|
std.debug.assert(context.scopes.items(.data)[block_scope_index] == .block);
|
||||||
|
return block_scope_index;
|
||||||
|
},
|
||||||
|
else => {
|
||||||
|
const new_scope = try context.pushScope(
|
||||||
|
offsets.nodeToLoc(context.tree, node_idx),
|
||||||
|
.other,
|
||||||
|
);
|
||||||
|
context.popScope();
|
||||||
|
return new_scope;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn makeScopeInternal(context: ScopeContext, node_idx: Ast.Node.Index) error{OutOfMemory}!void {
|
fn makeScopeInternal(context: ScopeContext, node_idx: Ast.Node.Index) error{OutOfMemory}!void {
|
||||||
if (node_idx == 0) return;
|
if (node_idx == 0) return;
|
||||||
|
|
||||||
@ -2662,25 +2692,6 @@ fn makeScopeInternal(context: ScopeContext, node_idx: Ast.Node.Index) error{OutO
|
|||||||
.block_two_semicolon,
|
.block_two_semicolon,
|
||||||
=> {
|
=> {
|
||||||
const first_token = tree.firstToken(node_idx);
|
const first_token = tree.firstToken(node_idx);
|
||||||
const last_token = ast.lastToken(tree, node_idx);
|
|
||||||
|
|
||||||
// if labeled block
|
|
||||||
if (token_tags[first_token] == .identifier) {
|
|
||||||
const scope_index = try context.pushScope(
|
|
||||||
.{
|
|
||||||
.start = offsets.tokenToIndex(tree, main_tokens[node_idx]),
|
|
||||||
.end = offsets.tokenToLoc(tree, last_token).start,
|
|
||||||
},
|
|
||||||
.other,
|
|
||||||
);
|
|
||||||
|
|
||||||
try scopes.items(.decls)[scope_index].putNoClobber(allocator, tree.tokenSlice(first_token), .{ .label_decl = .{
|
|
||||||
.label = first_token,
|
|
||||||
.block = node_idx,
|
|
||||||
} });
|
|
||||||
}
|
|
||||||
|
|
||||||
defer if (token_tags[first_token] == .identifier) context.popScope();
|
|
||||||
|
|
||||||
const scope_index = try context.pushScope(
|
const scope_index = try context.pushScope(
|
||||||
offsets.nodeToLoc(tree, node_idx),
|
offsets.nodeToLoc(tree, node_idx),
|
||||||
@ -2688,6 +2699,15 @@ fn makeScopeInternal(context: ScopeContext, node_idx: Ast.Node.Index) error{OutO
|
|||||||
);
|
);
|
||||||
defer context.popScope();
|
defer context.popScope();
|
||||||
|
|
||||||
|
// if labeled block
|
||||||
|
if (token_tags[first_token] == .identifier) {
|
||||||
|
try scopes.items(.decls)[scope_index].putNoClobber(
|
||||||
|
allocator,
|
||||||
|
tree.tokenSlice(first_token),
|
||||||
|
.{ .label_decl = .{ .label = first_token, .block = node_idx } },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
var buffer: [2]Ast.Node.Index = undefined;
|
var buffer: [2]Ast.Node.Index = undefined;
|
||||||
const statements = ast.blockStatements(tree, node_idx, &buffer).?;
|
const statements = ast.blockStatements(tree, node_idx, &buffer).?;
|
||||||
|
|
||||||
@ -2698,194 +2718,114 @@ fn makeScopeInternal(context: ScopeContext, node_idx: Ast.Node.Index) error{OutO
|
|||||||
try scopes.items(.decls)[scope_index].put(allocator, name, .{ .ast_node = idx });
|
try scopes.items(.decls)[scope_index].put(allocator, name, .{ .ast_node = idx });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
|
||||||
},
|
},
|
||||||
.@"if",
|
.@"if",
|
||||||
.if_simple,
|
.if_simple,
|
||||||
=> {
|
=> {
|
||||||
const if_node = ast.fullIf(tree, node_idx).?;
|
const if_node = ast.fullIf(tree, node_idx).?;
|
||||||
|
|
||||||
if (if_node.payload_token) |payload| {
|
const then_scope = (try makeBlockScopeInternal(context, if_node.ast.then_expr)).?;
|
||||||
const scope_index = try context.pushScope(
|
|
||||||
.{
|
|
||||||
.start = offsets.tokenToIndex(tree, payload),
|
|
||||||
.end = offsets.tokenToLoc(tree, ast.lastToken(tree, if_node.ast.then_expr)).end,
|
|
||||||
},
|
|
||||||
.other,
|
|
||||||
);
|
|
||||||
defer context.popScope();
|
|
||||||
|
|
||||||
|
if (if_node.payload_token) |payload| {
|
||||||
const name_token = payload + @boolToInt(token_tags[payload] == .asterisk);
|
const name_token = payload + @boolToInt(token_tags[payload] == .asterisk);
|
||||||
std.debug.assert(token_tags[name_token] == .identifier);
|
std.debug.assert(token_tags[name_token] == .identifier);
|
||||||
|
|
||||||
const name = tree.tokenSlice(name_token);
|
const name = tree.tokenSlice(name_token);
|
||||||
try scopes.items(.decls)[scope_index].putNoClobber(allocator, name, .{
|
try scopes.items(.decls)[then_scope].put(
|
||||||
.pointer_payload = .{
|
allocator,
|
||||||
.name = name_token,
|
name,
|
||||||
.condition = if_node.ast.cond_expr,
|
.{ .pointer_payload = .{ .name = name_token, .condition = if_node.ast.cond_expr } },
|
||||||
},
|
);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try makeScopeInternal(context, if_node.ast.then_expr);
|
|
||||||
|
|
||||||
if (if_node.ast.else_expr != 0) {
|
if (if_node.ast.else_expr != 0) {
|
||||||
|
const else_scope = (try makeBlockScopeInternal(context, if_node.ast.else_expr)).?;
|
||||||
if (if_node.error_token) |err_token| {
|
if (if_node.error_token) |err_token| {
|
||||||
std.debug.assert(token_tags[err_token] == .identifier);
|
|
||||||
const scope_index = try context.pushScope(
|
|
||||||
.{
|
|
||||||
.start = offsets.tokenToIndex(tree, err_token),
|
|
||||||
.end = offsets.tokenToLoc(tree, ast.lastToken(tree, if_node.ast.else_expr)).end,
|
|
||||||
},
|
|
||||||
.other,
|
|
||||||
);
|
|
||||||
defer context.popScope();
|
|
||||||
|
|
||||||
const name = tree.tokenSlice(err_token);
|
const name = tree.tokenSlice(err_token);
|
||||||
try scopes.items(.decls)[scope_index].putNoClobber(allocator, name, .{ .ast_node = if_node.ast.else_expr });
|
try scopes.items(.decls)[else_scope].put(allocator, name, .{ .ast_node = if_node.ast.else_expr });
|
||||||
}
|
}
|
||||||
try makeScopeInternal(context, if_node.ast.else_expr);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
.@"catch" => {
|
.@"catch" => {
|
||||||
try makeScopeInternal(context, data[node_idx].lhs);
|
try makeScopeInternal(context, data[node_idx].lhs);
|
||||||
|
|
||||||
const catch_token = main_tokens[node_idx];
|
const expr_scope = (try makeBlockScopeInternal(context, data[node_idx].rhs)).?;
|
||||||
const catch_expr = data[node_idx].rhs;
|
|
||||||
|
|
||||||
const scope_index = try context.pushScope(
|
const catch_token = main_tokens[node_idx] + 2;
|
||||||
.{
|
if (token_tags.len > catch_token and
|
||||||
.start = offsets.tokenToIndex(tree, tree.firstToken(catch_expr)),
|
token_tags[catch_token - 1] == .pipe and
|
||||||
.end = offsets.tokenToLoc(tree, ast.lastToken(tree, catch_expr)).end,
|
token_tags[catch_token] == .identifier)
|
||||||
},
|
|
||||||
.other,
|
|
||||||
);
|
|
||||||
defer context.popScope();
|
|
||||||
|
|
||||||
if (token_tags.len > catch_token + 2 and
|
|
||||||
token_tags[catch_token + 1] == .pipe and
|
|
||||||
token_tags[catch_token + 2] == .identifier)
|
|
||||||
{
|
{
|
||||||
const name = tree.tokenSlice(catch_token + 2);
|
const name = tree.tokenSlice(catch_token);
|
||||||
try scopes.items(.decls)[scope_index].putNoClobber(allocator, name, .{ .ast_node = catch_expr });
|
try scopes.items(.decls)[expr_scope].put(allocator, name, .{ .ast_node = data[node_idx].rhs });
|
||||||
}
|
}
|
||||||
try makeScopeInternal(context, catch_expr);
|
|
||||||
},
|
},
|
||||||
.@"while",
|
.@"while",
|
||||||
.while_simple,
|
.while_simple,
|
||||||
.while_cont,
|
.while_cont,
|
||||||
=> {
|
=> {
|
||||||
|
// label_token: inline_token while (cond_expr) |payload_token| : (cont_expr) then_expr else else_expr
|
||||||
const while_node = ast.fullWhile(tree, node_idx).?;
|
const while_node = ast.fullWhile(tree, node_idx).?;
|
||||||
|
|
||||||
|
try makeScopeInternal(context, while_node.ast.cond_expr);
|
||||||
|
|
||||||
|
const cont_scope = try makeBlockScopeInternal(context, while_node.ast.cont_expr);
|
||||||
|
const then_scope = (try makeBlockScopeInternal(context, while_node.ast.then_expr)).?;
|
||||||
|
const else_scope = try makeBlockScopeInternal(context, while_node.ast.else_expr);
|
||||||
|
|
||||||
if (while_node.label_token) |label| {
|
if (while_node.label_token) |label| {
|
||||||
std.debug.assert(token_tags[label] == .identifier);
|
std.debug.assert(token_tags[label] == .identifier);
|
||||||
|
|
||||||
const scope_index = try context.pushScope(
|
const name = tree.tokenSlice(label);
|
||||||
.{
|
try scopes.items(.decls)[then_scope].put(
|
||||||
.start = offsets.tokenToIndex(tree, while_node.ast.while_token),
|
|
||||||
.end = offsets.tokenToLoc(tree, ast.lastToken(tree, node_idx)).end,
|
|
||||||
},
|
|
||||||
.other,
|
|
||||||
);
|
|
||||||
|
|
||||||
try scopes.items(.decls)[scope_index].putNoClobber(
|
|
||||||
allocator,
|
allocator,
|
||||||
tree.tokenSlice(label),
|
name,
|
||||||
.{ .label_decl = .{ .label = label, .block = while_node.ast.then_expr } },
|
.{ .label_decl = .{ .label = label, .block = while_node.ast.then_expr } },
|
||||||
);
|
);
|
||||||
|
if (else_scope) |index| {
|
||||||
|
try scopes.items(.decls)[index].put(
|
||||||
|
allocator,
|
||||||
|
name,
|
||||||
|
.{ .label_decl = .{ .label = label, .block = while_node.ast.else_expr } },
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
defer if (while_node.label_token != null) context.popScope();
|
|
||||||
|
|
||||||
if (while_node.payload_token) |payload| {
|
if (while_node.payload_token) |payload| {
|
||||||
const scope_index = try context.pushScope(
|
|
||||||
.{
|
|
||||||
.start = offsets.tokenToIndex(tree, payload),
|
|
||||||
.end = offsets.tokenToLoc(tree, ast.lastToken(tree, while_node.ast.then_expr)).end,
|
|
||||||
},
|
|
||||||
.other,
|
|
||||||
);
|
|
||||||
defer context.popScope();
|
|
||||||
|
|
||||||
const name_token = payload + @boolToInt(token_tags[payload] == .asterisk);
|
const name_token = payload + @boolToInt(token_tags[payload] == .asterisk);
|
||||||
std.debug.assert(token_tags[name_token] == .identifier);
|
std.debug.assert(token_tags[name_token] == .identifier);
|
||||||
|
|
||||||
const name = tree.tokenSlice(name_token);
|
const name = tree.tokenSlice(name_token);
|
||||||
try scopes.items(.decls)[scope_index].putNoClobber(allocator, name, .{
|
const decl: Declaration = .{
|
||||||
.pointer_payload = .{
|
.pointer_payload = .{
|
||||||
.name = name_token,
|
.name = name_token,
|
||||||
.condition = while_node.ast.cond_expr,
|
.condition = while_node.ast.cond_expr,
|
||||||
},
|
},
|
||||||
});
|
};
|
||||||
|
if (cont_scope) |index| {
|
||||||
// for loop with index as well
|
try scopes.items(.decls)[index].put(allocator, name, decl);
|
||||||
if (token_tags[name_token + 1] == .comma) {
|
|
||||||
const index_token = name_token + 2;
|
|
||||||
std.debug.assert(token_tags[index_token] == .identifier);
|
|
||||||
try scopes.items(.decls)[scope_index].put(
|
|
||||||
allocator,
|
|
||||||
tree.tokenSlice(index_token),
|
|
||||||
.{ .array_index = index_token },
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
try scopes.items(.decls)[then_scope].put(allocator, name, decl);
|
||||||
}
|
}
|
||||||
try makeScopeInternal(context, while_node.ast.then_expr);
|
|
||||||
|
|
||||||
if (while_node.ast.else_expr != 0) {
|
if (while_node.error_token) |err_token| {
|
||||||
if (while_node.error_token) |err_token| {
|
std.debug.assert(token_tags[err_token] == .identifier);
|
||||||
std.debug.assert(token_tags[err_token] == .identifier);
|
const name = tree.tokenSlice(err_token);
|
||||||
const scope_index = try context.pushScope(
|
try scopes.items(.decls)[else_scope.?].put(allocator, name, .{ .ast_node = while_node.ast.else_expr });
|
||||||
.{
|
|
||||||
.start = offsets.tokenToIndex(tree, err_token),
|
|
||||||
.end = offsets.tokenToLoc(tree, ast.lastToken(tree, while_node.ast.else_expr)).end,
|
|
||||||
},
|
|
||||||
.other,
|
|
||||||
);
|
|
||||||
defer context.popScope();
|
|
||||||
|
|
||||||
const name = tree.tokenSlice(err_token);
|
|
||||||
try scopes.items(.decls)[scope_index].putNoClobber(
|
|
||||||
allocator,
|
|
||||||
name,
|
|
||||||
.{ .ast_node = while_node.ast.else_expr },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
try makeScopeInternal(context, while_node.ast.else_expr);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
.@"for",
|
.@"for",
|
||||||
.for_simple,
|
.for_simple,
|
||||||
=> {
|
=> {
|
||||||
|
// label_token: inline_token for (inputs) |capture_tokens| then_expr else else_expr
|
||||||
const for_node = ast.fullFor(tree, node_idx).?;
|
const for_node = ast.fullFor(tree, node_idx).?;
|
||||||
|
|
||||||
if (for_node.label_token) |label| {
|
for (for_node.ast.inputs) |input_node| {
|
||||||
std.debug.assert(token_tags[label] == .identifier);
|
try makeScopeInternal(context, input_node);
|
||||||
const scope_index = try context.pushScope(
|
|
||||||
.{
|
|
||||||
.start = offsets.tokenToIndex(tree, for_node.ast.for_token),
|
|
||||||
.end = offsets.tokenToLoc(tree, ast.lastToken(tree, node_idx)).end,
|
|
||||||
},
|
|
||||||
.other,
|
|
||||||
);
|
|
||||||
|
|
||||||
try scopes.items(.decls)[scope_index].putNoClobber(
|
|
||||||
allocator,
|
|
||||||
tree.tokenSlice(label),
|
|
||||||
.{ .label_decl = .{ .label = label, .block = for_node.ast.then_expr } },
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
defer if (for_node.label_token != null) context.popScope();
|
const then_scope = (try makeBlockScopeInternal(context, for_node.ast.then_expr)).?;
|
||||||
|
const else_scope = try makeBlockScopeInternal(context, for_node.ast.else_expr);
|
||||||
const scope_index = try context.pushScope(
|
|
||||||
.{
|
|
||||||
.start = offsets.tokenToIndex(tree, for_node.payload_token),
|
|
||||||
.end = offsets.tokenToLoc(tree, ast.lastToken(tree, for_node.ast.then_expr)).end,
|
|
||||||
},
|
|
||||||
.other,
|
|
||||||
);
|
|
||||||
defer context.popScope();
|
|
||||||
|
|
||||||
var capture_token = for_node.payload_token;
|
var capture_token = for_node.payload_token;
|
||||||
for (for_node.ast.inputs) |input| {
|
for (for_node.ast.inputs) |input| {
|
||||||
@ -2894,19 +2834,29 @@ fn makeScopeInternal(context: ScopeContext, node_idx: Ast.Node.Index) error{OutO
|
|||||||
const name_token = capture_token + @boolToInt(capture_is_ref);
|
const name_token = capture_token + @boolToInt(capture_is_ref);
|
||||||
capture_token = name_token + 2;
|
capture_token = name_token + 2;
|
||||||
|
|
||||||
const name = offsets.tokenToSlice(tree, name_token);
|
try scopes.items(.decls)[then_scope].put(
|
||||||
try scopes.items(.decls)[scope_index].put(allocator, name, .{
|
allocator,
|
||||||
.array_payload = .{
|
offsets.tokenToSlice(tree, name_token),
|
||||||
.identifier = name_token,
|
.{ .array_payload = .{ .identifier = name_token, .array_expr = input } },
|
||||||
.array_expr = input,
|
);
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try makeScopeInternal(context, for_node.ast.then_expr);
|
if (for_node.label_token) |label| {
|
||||||
|
std.debug.assert(token_tags[label] == .identifier);
|
||||||
|
|
||||||
if (for_node.ast.else_expr != 0) {
|
const name = tree.tokenSlice(label);
|
||||||
try makeScopeInternal(context, for_node.ast.else_expr);
|
try scopes.items(.decls)[then_scope].put(
|
||||||
|
allocator,
|
||||||
|
name,
|
||||||
|
.{ .label_decl = .{ .label = label, .block = for_node.ast.then_expr } },
|
||||||
|
);
|
||||||
|
if (else_scope) |index| {
|
||||||
|
try scopes.items(.decls)[index].put(
|
||||||
|
allocator,
|
||||||
|
name,
|
||||||
|
.{ .label_decl = .{ .label = label, .block = for_node.ast.else_expr } },
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
.@"switch",
|
.@"switch",
|
||||||
@ -2920,29 +2870,21 @@ fn makeScopeInternal(context: ScopeContext, node_idx: Ast.Node.Index) error{OutO
|
|||||||
const switch_case: Ast.full.SwitchCase = tree.fullSwitchCase(case).?;
|
const switch_case: Ast.full.SwitchCase = tree.fullSwitchCase(case).?;
|
||||||
|
|
||||||
if (switch_case.payload_token) |payload| {
|
if (switch_case.payload_token) |payload| {
|
||||||
const scope_index = try context.pushScope(
|
const expr_index = (try makeBlockScopeInternal(context, switch_case.ast.target_expr)).?;
|
||||||
.{
|
|
||||||
.start = offsets.tokenToIndex(tree, payload),
|
|
||||||
.end = offsets.tokenToLoc(tree, ast.lastToken(tree, switch_case.ast.target_expr)).end,
|
|
||||||
},
|
|
||||||
.other,
|
|
||||||
);
|
|
||||||
defer context.popScope();
|
|
||||||
|
|
||||||
// if payload is *name than get next token
|
// if payload is *name than get next token
|
||||||
const name_token = payload + @boolToInt(token_tags[payload] == .asterisk);
|
const name_token = payload + @boolToInt(token_tags[payload] == .asterisk);
|
||||||
const name = tree.tokenSlice(name_token);
|
const name = tree.tokenSlice(name_token);
|
||||||
|
|
||||||
try scopes.items(.decls)[scope_index].putNoClobber(allocator, name, .{
|
try scopes.items(.decls)[expr_index].put(allocator, name, .{
|
||||||
.switch_payload = .{
|
.switch_payload = .{
|
||||||
.node = name_token,
|
.node = name_token,
|
||||||
.switch_expr = cond,
|
.switch_expr = cond,
|
||||||
.items = switch_case.ast.values,
|
.items = switch_case.ast.values,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
try makeScopeInternal(context, switch_case.ast.target_expr);
|
||||||
}
|
}
|
||||||
|
|
||||||
try makeScopeInternal(context, switch_case.ast.target_expr);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
.global_var_decl,
|
.global_var_decl,
|
||||||
@ -3064,23 +3006,13 @@ fn makeScopeInternal(context: ScopeContext, node_idx: Ast.Node.Index) error{OutO
|
|||||||
try makeScopeInternal(context, slice.ast.sentinel);
|
try makeScopeInternal(context, slice.ast.sentinel);
|
||||||
},
|
},
|
||||||
.@"errdefer" => {
|
.@"errdefer" => {
|
||||||
const expr = data[node_idx].rhs;
|
const expr_scope = (try makeBlockScopeInternal(context, data[node_idx].rhs)).?;
|
||||||
if (data[node_idx].lhs != 0) {
|
|
||||||
const payload_token = data[node_idx].lhs;
|
|
||||||
const scope_index = try context.pushScope(
|
|
||||||
.{
|
|
||||||
.start = offsets.tokenToIndex(tree, payload_token),
|
|
||||||
.end = offsets.tokenToLoc(tree, ast.lastToken(tree, expr)).end,
|
|
||||||
},
|
|
||||||
.other,
|
|
||||||
);
|
|
||||||
defer context.popScope();
|
|
||||||
|
|
||||||
|
const payload_token = data[node_idx].lhs;
|
||||||
|
if (payload_token != 0) {
|
||||||
const name = tree.tokenSlice(payload_token);
|
const name = tree.tokenSlice(payload_token);
|
||||||
try scopes.items(.decls)[scope_index].putNoClobber(allocator, name, .{ .ast_node = expr });
|
try scopes.items(.decls)[expr_scope].putNoClobber(allocator, name, .{ .ast_node = data[node_idx].rhs });
|
||||||
}
|
}
|
||||||
|
|
||||||
try makeScopeInternal(context, expr);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
.switch_case,
|
.switch_case,
|
||||||
|
Loading…
Reference in New Issue
Block a user