mirror of
https://github.com/gcc-mirror/gcc.git
synced 2026-05-06 14:59:39 +02:00
gccrs: expand: Keep track of semicoloned builtin macros
This is quite a rough fix (like a lot of the macro expansion code...) but
it allows built-in macros to be treated as statements. I *think* asm!()
might be the only one where it really matters, but also doing something
like
{
line!();
}
will now work, whereas before the macro invocation would not get expanded
properly and would be ignored.
gcc/rust/ChangeLog:
* ast/rust-ast-fragment.h: Pass `is_semicolon` information to builtin
macro transcribers.
* ast/rust-macro.h: Pass semicolon information to transcriber..
* expand/rust-macro-builtins-asm.cc (MacroBuiltin::asm_handler): Likewise.
(parse_asm): Likewise.
* expand/rust-macro-builtins-asm.h (parse_asm): Likewise.
* expand/rust-macro-builtins-format-args.cc (MacroBuiltin::format_args_handler): Likewise.
* expand/rust-macro-builtins-include.cc (MacroBuiltin::include_bytes_handler): Likewise.
(MacroBuiltin::include_str_handler): Likewise.
(MacroBuiltin::include_handler): Likewise.
* expand/rust-macro-builtins-location.cc (MacroBuiltin::file_handler): Likewise.
(MacroBuiltin::column_handler): Likewise.
(MacroBuiltin::line_handler): Likewise.
* expand/rust-macro-builtins-log-debug.cc (MacroBuiltin::assert_handler): Likewise.
* expand/rust-macro-builtins-utility.cc (MacroBuiltin::compile_error_handler): Likewise.
(MacroBuiltin::concat_handler): Likewise.
(MacroBuiltin::env_handler): Likewise.
(MacroBuiltin::cfg_handler): Likewise.
(MacroBuiltin::stringify_handler): Likewise.
* expand/rust-macro-builtins.cc (format_args_maker): Likewise.
(inline_asm_maker): Likewise.
(MacroBuiltin::sorry): Likewise.
(MacroBuiltin::proc_macro_builtin): Likewise.
* expand/rust-macro-builtins.h: Likewise.
* expand/rust-macro-expand.cc (MacroExpander::expand_decl_macro): Likewise.
(MacroExpander::expand_invoc): Likewise.
* ast/rust-ast-visitor.cc (DefaultASTVisitor::visit): Remove rust_unreachable.
* ast/rust-ast.cc (InlineAsm::accept_vis): Likewise.
* hir/tree/rust-hir.cc (InlineAsm::accept_vis): Likewise.
This commit is contained in:
@@ -128,7 +128,8 @@ private:
|
||||
* rust-macro-builtins.{h,cc}.
|
||||
*/
|
||||
using MacroTranscriberFunc
|
||||
= std::function<tl::optional<Fragment> (location_t, MacroInvocData &)>;
|
||||
= std::function<tl::optional<Fragment> (location_t, MacroInvocData &,
|
||||
bool semicolon)>;
|
||||
|
||||
} // namespace AST
|
||||
} // namespace Rust
|
||||
|
||||
@@ -664,9 +664,7 @@ DefaultASTVisitor::visit (AST::AsyncBlockExpr &expr)
|
||||
|
||||
void
|
||||
DefaultASTVisitor::visit (AST::InlineAsm &expr)
|
||||
{
|
||||
rust_unreachable ();
|
||||
}
|
||||
{}
|
||||
|
||||
void
|
||||
DefaultASTVisitor::visit (AST::TypeParam ¶m)
|
||||
|
||||
@@ -4659,7 +4659,6 @@ AsyncBlockExpr::accept_vis (ASTVisitor &vis)
|
||||
void
|
||||
InlineAsm::accept_vis (ASTVisitor &vis)
|
||||
{
|
||||
rust_unreachable ();
|
||||
vis.visit (*this);
|
||||
}
|
||||
|
||||
|
||||
@@ -482,7 +482,7 @@ private:
|
||||
* should make use of the actual rules. If the macro is builtin, then another
|
||||
* associated transcriber should be used
|
||||
*/
|
||||
static Fragment dummy_builtin (location_t, MacroInvocData &)
|
||||
static Fragment dummy_builtin (location_t, MacroInvocData &, bool)
|
||||
{
|
||||
rust_unreachable ();
|
||||
return Fragment::create_error ();
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
// <http://www.gnu.org/licenses/>.
|
||||
|
||||
#include "rust-macro-builtins-asm.h"
|
||||
#include "rust-ast.h"
|
||||
#include "rust-stmt.h"
|
||||
|
||||
namespace Rust {
|
||||
std::map<AST::InlineAsmOption, std::string> InlineAsmOptionMap{
|
||||
@@ -535,9 +537,9 @@ parse_format_string (InlineAsmContext &inline_asm_ctx)
|
||||
|
||||
tl::optional<AST::Fragment>
|
||||
MacroBuiltin::asm_handler (location_t invoc_locus, AST::MacroInvocData &invoc,
|
||||
bool is_global_asm)
|
||||
bool semicolon, bool is_global_asm)
|
||||
{
|
||||
return parse_asm (invoc_locus, invoc, is_global_asm);
|
||||
return parse_asm (invoc_locus, invoc, is_global_asm, semicolon);
|
||||
}
|
||||
|
||||
tl::expected<InlineAsmContext, std::string>
|
||||
@@ -607,7 +609,7 @@ parse_asm_arg (InlineAsmContext inline_asm_ctx)
|
||||
|
||||
tl::optional<AST::Fragment>
|
||||
parse_asm (location_t invoc_locus, AST::MacroInvocData &invoc,
|
||||
bool is_global_asm)
|
||||
bool is_global_asm, bool semicolon)
|
||||
{
|
||||
// From the rule of asm.
|
||||
// We first peek and see if it is a format string or not.
|
||||
@@ -646,9 +648,18 @@ parse_asm (location_t invoc_locus, AST::MacroInvocData &invoc,
|
||||
|
||||
if (is_valid)
|
||||
{
|
||||
AST::SingleASTNode single = AST::SingleASTNode (
|
||||
inline_asm_ctx.inline_asm.clone_expr_without_block ());
|
||||
std::vector<AST::SingleASTNode> single_vec = {single};
|
||||
auto node = inline_asm_ctx.inline_asm.clone_expr_without_block ();
|
||||
|
||||
std::vector<AST::SingleASTNode> single_vec = {};
|
||||
|
||||
// If the macro invocation has a semicolon (`asm!("...");`), then we need
|
||||
// to make it a statement. This way, it will be expanded properly.
|
||||
if (semicolon)
|
||||
single_vec.emplace_back (
|
||||
AST::SingleASTNode (std::unique_ptr<AST::Stmt> (
|
||||
new AST::ExprStmt (std::move (node), invoc_locus, semicolon))));
|
||||
else
|
||||
single_vec.emplace_back (AST::SingleASTNode (std::move (node)));
|
||||
|
||||
AST::Fragment fragment_ast
|
||||
= AST::Fragment (single_vec,
|
||||
|
||||
@@ -59,7 +59,7 @@ parse_reg_operand (InlineAsmContext inline_asm_ctx);
|
||||
|
||||
tl::optional<AST::Fragment>
|
||||
parse_asm (location_t invoc_locus, AST::MacroInvocData &invoc,
|
||||
bool is_global_asm);
|
||||
bool is_global_asm, bool semicolon);
|
||||
|
||||
bool
|
||||
check_identifier (Parser<MacroInvocLexer> &parser, std::string ident);
|
||||
|
||||
@@ -115,7 +115,7 @@ format_args_parse_arguments (AST::MacroInvocData &invoc)
|
||||
|
||||
tl::optional<AST::Fragment>
|
||||
MacroBuiltin::format_args_handler (location_t invoc_locus,
|
||||
AST::MacroInvocData &invoc,
|
||||
AST::MacroInvocData &invoc, bool semicolon,
|
||||
AST::FormatArgs::Newline nl)
|
||||
{
|
||||
auto input = format_args_parse_arguments (invoc);
|
||||
|
||||
@@ -27,7 +27,7 @@ of the given file as reference to a byte array. Yields an expression of type
|
||||
|
||||
tl::optional<AST::Fragment>
|
||||
MacroBuiltin::include_bytes_handler (location_t invoc_locus,
|
||||
AST::MacroInvocData &invoc)
|
||||
AST::MacroInvocData &invoc, bool semicolon)
|
||||
{
|
||||
/* Get target filename from the macro invocation, which is treated as a path
|
||||
relative to the include!-ing file (currently being compiled). */
|
||||
@@ -93,7 +93,7 @@ MacroBuiltin::include_bytes_handler (location_t invoc_locus,
|
||||
|
||||
tl::optional<AST::Fragment>
|
||||
MacroBuiltin::include_str_handler (location_t invoc_locus,
|
||||
AST::MacroInvocData &invoc)
|
||||
AST::MacroInvocData &invoc, bool semicolon)
|
||||
{
|
||||
/* Get target filename from the macro invocation, which is treated as a path
|
||||
relative to the include!-ing file (currently being compiled). */
|
||||
@@ -182,7 +182,7 @@ scope compile time. */
|
||||
|
||||
tl::optional<AST::Fragment>
|
||||
MacroBuiltin::include_handler (location_t invoc_locus,
|
||||
AST::MacroInvocData &invoc)
|
||||
AST::MacroInvocData &invoc, bool semicolon)
|
||||
{
|
||||
/* Get target filename from the macro invocation, which is treated as a path
|
||||
relative to the include!-ing file (currently being compiled). */
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
namespace Rust {
|
||||
tl::optional<AST::Fragment>
|
||||
MacroBuiltin::file_handler (location_t invoc_locus, AST::MacroInvocData &)
|
||||
MacroBuiltin::file_handler (location_t invoc_locus, AST::MacroInvocData &, bool)
|
||||
{
|
||||
auto current_file = LOCATION_FILE (invoc_locus);
|
||||
auto file_str = AST::SingleASTNode (make_string (invoc_locus, current_file));
|
||||
@@ -32,7 +32,8 @@ MacroBuiltin::file_handler (location_t invoc_locus, AST::MacroInvocData &)
|
||||
}
|
||||
|
||||
tl::optional<AST::Fragment>
|
||||
MacroBuiltin::column_handler (location_t invoc_locus, AST::MacroInvocData &)
|
||||
MacroBuiltin::column_handler (location_t invoc_locus, AST::MacroInvocData &,
|
||||
bool)
|
||||
{
|
||||
auto current_column = LOCATION_COLUMN (invoc_locus);
|
||||
|
||||
@@ -46,7 +47,7 @@ MacroBuiltin::column_handler (location_t invoc_locus, AST::MacroInvocData &)
|
||||
}
|
||||
|
||||
tl::optional<AST::Fragment>
|
||||
MacroBuiltin::line_handler (location_t invoc_locus, AST::MacroInvocData &)
|
||||
MacroBuiltin::line_handler (location_t invoc_locus, AST::MacroInvocData &, bool)
|
||||
{
|
||||
auto current_line = LOCATION_LINE (invoc_locus);
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
namespace Rust {
|
||||
tl::optional<AST::Fragment>
|
||||
MacroBuiltin::assert_handler (location_t invoc_locus,
|
||||
AST::MacroInvocData &invoc)
|
||||
AST::MacroInvocData &invoc, bool semicolon)
|
||||
{
|
||||
rust_debug ("assert!() called");
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace Rust {
|
||||
during the compile time. */
|
||||
tl::optional<AST::Fragment>
|
||||
MacroBuiltin::compile_error_handler (location_t invoc_locus,
|
||||
AST::MacroInvocData &invoc)
|
||||
AST::MacroInvocData &invoc, bool semicolon)
|
||||
{
|
||||
auto lit_expr
|
||||
= parse_single_string_literal (BuiltinMacro::CompileError,
|
||||
@@ -87,7 +87,7 @@ MacroBuiltin::compile_error_handler (location_t invoc_locus,
|
||||
// Can we do that easily?
|
||||
tl::optional<AST::Fragment>
|
||||
MacroBuiltin::concat_handler (location_t invoc_locus,
|
||||
AST::MacroInvocData &invoc)
|
||||
AST::MacroInvocData &invoc, bool semicolon)
|
||||
{
|
||||
auto invoc_token_tree = invoc.get_delim_tok_tree ();
|
||||
MacroInvocLexer lex (invoc_token_tree.to_token_stream ());
|
||||
@@ -151,7 +151,8 @@ MacroBuiltin::concat_handler (location_t invoc_locus,
|
||||
/* Expand builtin macro env!(), which inspects an environment variable at
|
||||
compile time. */
|
||||
tl::optional<AST::Fragment>
|
||||
MacroBuiltin::env_handler (location_t invoc_locus, AST::MacroInvocData &invoc)
|
||||
MacroBuiltin::env_handler (location_t invoc_locus, AST::MacroInvocData &invoc,
|
||||
bool semicolon)
|
||||
{
|
||||
auto invoc_token_tree = invoc.get_delim_tok_tree ();
|
||||
MacroInvocLexer lex (invoc_token_tree.to_token_stream ());
|
||||
@@ -224,7 +225,8 @@ MacroBuiltin::env_handler (location_t invoc_locus, AST::MacroInvocData &invoc)
|
||||
}
|
||||
|
||||
tl::optional<AST::Fragment>
|
||||
MacroBuiltin::cfg_handler (location_t invoc_locus, AST::MacroInvocData &invoc)
|
||||
MacroBuiltin::cfg_handler (location_t invoc_locus, AST::MacroInvocData &invoc,
|
||||
bool semicolon)
|
||||
{
|
||||
// only parse if not already parsed
|
||||
if (!invoc.is_parsed ())
|
||||
@@ -263,7 +265,7 @@ MacroBuiltin::cfg_handler (location_t invoc_locus, AST::MacroInvocData &invoc)
|
||||
|
||||
tl::optional<AST::Fragment>
|
||||
MacroBuiltin::stringify_handler (location_t invoc_locus,
|
||||
AST::MacroInvocData &invoc)
|
||||
AST::MacroInvocData &invoc, bool semicolon)
|
||||
{
|
||||
std::string content;
|
||||
auto invoc_token_tree = invoc.get_delim_tok_tree ();
|
||||
|
||||
@@ -87,8 +87,8 @@ const BiMap<std::string, BuiltinMacro> MacroBuiltin::builtins = {{
|
||||
AST::MacroTranscriberFunc
|
||||
format_args_maker (AST::FormatArgs::Newline nl)
|
||||
{
|
||||
return [nl] (location_t loc, AST::MacroInvocData &invoc) {
|
||||
return MacroBuiltin::format_args_handler (loc, invoc, nl);
|
||||
return [nl] (location_t loc, AST::MacroInvocData &invoc, bool semicolon) {
|
||||
return MacroBuiltin::format_args_handler (loc, invoc, semicolon, nl);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -103,9 +103,10 @@ inline_asm_maker (isGlobalAsm is_global_asm)
|
||||
{
|
||||
bool global_asm = is_global_asm == isGlobalAsm::Global ? true : false;
|
||||
|
||||
return [global_asm] (location_t loc, AST::MacroInvocData &invoc) {
|
||||
return MacroBuiltin::asm_handler (loc, invoc, global_asm);
|
||||
};
|
||||
return
|
||||
[global_asm] (location_t loc, AST::MacroInvocData &invoc, bool semicolon) {
|
||||
return MacroBuiltin::asm_handler (loc, invoc, semicolon, global_asm);
|
||||
};
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, AST::MacroTranscriberFunc>
|
||||
@@ -167,7 +168,8 @@ builtin_macro_from_string (const std::string &identifier)
|
||||
}
|
||||
|
||||
tl::optional<AST::Fragment>
|
||||
MacroBuiltin::sorry (location_t invoc_locus, AST::MacroInvocData &invoc)
|
||||
MacroBuiltin::sorry (location_t invoc_locus, AST::MacroInvocData &invoc,
|
||||
bool semicolon)
|
||||
{
|
||||
rust_sorry_at (invoc_locus, "unimplemented builtin macro: %qs",
|
||||
invoc.get_path ().as_string ().c_str ());
|
||||
@@ -177,7 +179,7 @@ MacroBuiltin::sorry (location_t invoc_locus, AST::MacroInvocData &invoc)
|
||||
|
||||
tl::optional<AST::Fragment>
|
||||
MacroBuiltin::proc_macro_builtin (location_t invoc_locus,
|
||||
AST::MacroInvocData &invoc)
|
||||
AST::MacroInvocData &invoc, bool semicolon)
|
||||
{
|
||||
rust_error_at (invoc_locus, "cannot invoke derive macro: %qs",
|
||||
invoc.get_path ().as_string ().c_str ());
|
||||
|
||||
@@ -123,57 +123,70 @@ public:
|
||||
static std::unordered_map<std::string, AST::MacroTranscriberFunc>
|
||||
builtin_transcribers;
|
||||
|
||||
static tl::optional<AST::Fragment>
|
||||
assert_handler (location_t invoc_locus, AST::MacroInvocData &invoc);
|
||||
static tl::optional<AST::Fragment> assert_handler (location_t invoc_locus,
|
||||
AST::MacroInvocData &invoc,
|
||||
bool semicolon);
|
||||
|
||||
static tl::optional<AST::Fragment> file_handler (location_t invoc_locus,
|
||||
AST::MacroInvocData &invoc);
|
||||
AST::MacroInvocData &invoc,
|
||||
bool semicolon);
|
||||
|
||||
static tl::optional<AST::Fragment> column_handler (location_t invoc_locus,
|
||||
AST::MacroInvocData &invoc,
|
||||
bool semicolon);
|
||||
|
||||
static tl::optional<AST::Fragment>
|
||||
column_handler (location_t invoc_locus, AST::MacroInvocData &invoc);
|
||||
include_bytes_handler (location_t invoc_locus, AST::MacroInvocData &invoc,
|
||||
bool semicolon);
|
||||
|
||||
static tl::optional<AST::Fragment>
|
||||
include_bytes_handler (location_t invoc_locus, AST::MacroInvocData &invoc);
|
||||
include_str_handler (location_t invoc_locus, AST::MacroInvocData &invoc,
|
||||
bool semicolon);
|
||||
|
||||
static tl::optional<AST::Fragment>
|
||||
include_str_handler (location_t invoc_locus, AST::MacroInvocData &invoc);
|
||||
stringify_handler (location_t invoc_locus, AST::MacroInvocData &invoc,
|
||||
bool semicolon);
|
||||
|
||||
static tl::optional<AST::Fragment>
|
||||
stringify_handler (location_t invoc_locus, AST::MacroInvocData &invoc);
|
||||
compile_error_handler (location_t invoc_locus, AST::MacroInvocData &invoc,
|
||||
bool semicolon);
|
||||
|
||||
static tl::optional<AST::Fragment>
|
||||
compile_error_handler (location_t invoc_locus, AST::MacroInvocData &invoc);
|
||||
|
||||
static tl::optional<AST::Fragment>
|
||||
concat_handler (location_t invoc_locus, AST::MacroInvocData &invoc);
|
||||
static tl::optional<AST::Fragment> concat_handler (location_t invoc_locus,
|
||||
AST::MacroInvocData &invoc,
|
||||
bool semicolon);
|
||||
|
||||
static tl::optional<AST::Fragment> env_handler (location_t invoc_locus,
|
||||
AST::MacroInvocData &invoc);
|
||||
AST::MacroInvocData &invoc,
|
||||
bool semicolon);
|
||||
|
||||
static tl::optional<AST::Fragment> cfg_handler (location_t invoc_locus,
|
||||
AST::MacroInvocData &invoc);
|
||||
AST::MacroInvocData &invoc,
|
||||
bool semicolon);
|
||||
|
||||
static tl::optional<AST::Fragment>
|
||||
include_handler (location_t invoc_locus, AST::MacroInvocData &invoc);
|
||||
include_handler (location_t invoc_locus, AST::MacroInvocData &invoc,
|
||||
bool semicolon);
|
||||
|
||||
static tl::optional<AST::Fragment> line_handler (location_t invoc_locus,
|
||||
AST::MacroInvocData &invoc);
|
||||
AST::MacroInvocData &invoc,
|
||||
bool semicolon);
|
||||
|
||||
static tl::optional<AST::Fragment> asm_handler (location_t invoc_locus,
|
||||
AST::MacroInvocData &invoc,
|
||||
bool semicolon,
|
||||
bool is_global_asm);
|
||||
|
||||
static tl::optional<AST::Fragment>
|
||||
format_args_handler (location_t invoc_locus, AST::MacroInvocData &invoc,
|
||||
AST::FormatArgs::Newline nl);
|
||||
bool semicolon, AST::FormatArgs::Newline nl);
|
||||
|
||||
static tl::optional<AST::Fragment> sorry (location_t invoc_locus,
|
||||
AST::MacroInvocData &invoc);
|
||||
static tl::optional<AST::Fragment>
|
||||
sorry (location_t invoc_locus, AST::MacroInvocData &invoc, bool semicolon);
|
||||
|
||||
/* Builtin procedural macros do not work directly on tokens, but still need a
|
||||
* builtin transcriber to be considered proper builtin macros */
|
||||
static tl::optional<AST::Fragment> proc_macro_builtin (location_t,
|
||||
AST::MacroInvocData &);
|
||||
static tl::optional<AST::Fragment>
|
||||
proc_macro_builtin (location_t, AST::MacroInvocData &, bool);
|
||||
};
|
||||
} // namespace Rust
|
||||
|
||||
|
||||
@@ -43,9 +43,10 @@ MacroExpander::expand_decl_macro (location_t invoc_locus,
|
||||
|
||||
/* probably something here about parsing invoc and rules def token trees to
|
||||
* token stream. if not, how would parser handle the captures of exprs and
|
||||
* stuff? on the other hand, token trees may be kind of useful in rules def as
|
||||
* creating a point where recursion can occur (like having
|
||||
* "compare_macro_match" and then it calling itself when it finds delimiters)
|
||||
* stuff? on the other hand, token trees may be kind of useful in rules def
|
||||
* as creating a point where recursion can occur (like having
|
||||
* "compare_macro_match" and then it calling itself when it finds
|
||||
* delimiters)
|
||||
*/
|
||||
|
||||
/* find matching rule to invoc token tree, based on macro rule's matcher. if
|
||||
@@ -70,9 +71,10 @@ MacroExpander::expand_decl_macro (location_t invoc_locus,
|
||||
|
||||
/* TODO: it is probably better to modify AST::Token to store a pointer to a
|
||||
* Lexer::Token (rather than being converted) - i.e. not so much have
|
||||
* AST::Token as a Token but rather a TokenContainer (as it is another type of
|
||||
* TokenTree). This will prevent re-conversion of Tokens between each type
|
||||
* all the time, while still allowing the heterogenous storage of token trees.
|
||||
* AST::Token as a Token but rather a TokenContainer (as it is another type
|
||||
* of TokenTree). This will prevent re-conversion of Tokens between each
|
||||
* type all the time, while still allowing the heterogenous storage of token
|
||||
* trees.
|
||||
*/
|
||||
|
||||
AST::DelimTokenTree &invoc_token_tree = invoc.get_delim_tok_tree ();
|
||||
@@ -286,8 +288,12 @@ MacroExpander::expand_invoc (AST::MacroInvocation &invoc, bool has_semicolon)
|
||||
last_invoc = *invoc.clone_macro_invocation_impl ();
|
||||
last_def = *rdef;
|
||||
|
||||
rust_debug ("[ARTHUR] semicolon: %s", has_semicolon ? "yes" : "no");
|
||||
|
||||
if (rdef->is_builtin ())
|
||||
fragment = rdef->get_builtin_transcriber () (invoc.get_locus (), invoc_data)
|
||||
fragment = rdef
|
||||
->get_builtin_transcriber () (invoc.get_locus (), invoc_data,
|
||||
has_semicolon)
|
||||
.value_or (AST::Fragment::create_empty ());
|
||||
else
|
||||
fragment = expand_decl_macro (invoc.get_locus (), invoc_data, *rdef,
|
||||
|
||||
@@ -3772,14 +3772,11 @@ BorrowExpr::accept_vis (HIRFullVisitor &vis)
|
||||
|
||||
void
|
||||
InlineAsm::accept_vis (HIRExpressionVisitor &vis)
|
||||
{
|
||||
rust_unreachable ();
|
||||
}
|
||||
{}
|
||||
|
||||
void
|
||||
InlineAsm::accept_vis (HIRFullVisitor &vis)
|
||||
{
|
||||
rust_unreachable ();
|
||||
vis.visit (*this);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user