mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
[Sema] Split out result builder pre-checking
Use `preCheckTarget` to pre-check the body, allowing us to replace `PreCheckResultBuilderRequest` with a request that only checks the brace for ReturnStmts.
This commit is contained in:
@@ -3028,60 +3028,8 @@ public:
|
|||||||
void cacheResult(ProtocolConformanceRef value) const;
|
void cacheResult(ProtocolConformanceRef value) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PreCheckResultBuilderDescriptor {
|
class BraceHasReturnRequest
|
||||||
AnyFunctionRef Fn;
|
: public SimpleRequest<BraceHasReturnRequest, bool(const BraceStmt *),
|
||||||
|
|
||||||
private:
|
|
||||||
// NOTE: Since source tooling (e.g. code completion) might replace the body,
|
|
||||||
// we need to take the body into account to calculate 'hash_value' and '=='.
|
|
||||||
// Also, we cannot 'getBody()' inside 'hash_value' and '==' because it invokes
|
|
||||||
// another request (even if it's cached).
|
|
||||||
BraceStmt *Body;
|
|
||||||
|
|
||||||
public:
|
|
||||||
PreCheckResultBuilderDescriptor(AnyFunctionRef Fn)
|
|
||||||
: Fn(Fn), Body(Fn.getBody()) {}
|
|
||||||
|
|
||||||
friend llvm::hash_code
|
|
||||||
hash_value(const PreCheckResultBuilderDescriptor &owner) {
|
|
||||||
return llvm::hash_combine(owner.Fn, owner.Body);
|
|
||||||
}
|
|
||||||
|
|
||||||
friend bool operator==(const PreCheckResultBuilderDescriptor &lhs,
|
|
||||||
const PreCheckResultBuilderDescriptor &rhs) {
|
|
||||||
return lhs.Fn == rhs.Fn && lhs.Body == rhs.Body;
|
|
||||||
}
|
|
||||||
|
|
||||||
friend bool operator!=(const PreCheckResultBuilderDescriptor &lhs,
|
|
||||||
const PreCheckResultBuilderDescriptor &rhs) {
|
|
||||||
return !(lhs == rhs);
|
|
||||||
}
|
|
||||||
|
|
||||||
friend SourceLoc extractNearestSourceLoc(PreCheckResultBuilderDescriptor d) {
|
|
||||||
return extractNearestSourceLoc(d.Fn);
|
|
||||||
}
|
|
||||||
|
|
||||||
friend void simple_display(llvm::raw_ostream &out,
|
|
||||||
const PreCheckResultBuilderDescriptor &d) {
|
|
||||||
simple_display(out, d.Fn);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class ResultBuilderBodyPreCheck : uint8_t {
|
|
||||||
/// There were no problems pre-checking the closure.
|
|
||||||
Okay,
|
|
||||||
|
|
||||||
/// There was an error pre-checking the closure.
|
|
||||||
Error,
|
|
||||||
|
|
||||||
/// The closure has a return statement.
|
|
||||||
HasReturnStmt,
|
|
||||||
};
|
|
||||||
|
|
||||||
class PreCheckResultBuilderRequest
|
|
||||||
: public SimpleRequest<PreCheckResultBuilderRequest,
|
|
||||||
ResultBuilderBodyPreCheck(
|
|
||||||
PreCheckResultBuilderDescriptor),
|
|
||||||
RequestFlags::Cached> {
|
RequestFlags::Cached> {
|
||||||
public:
|
public:
|
||||||
using SimpleRequest::SimpleRequest;
|
using SimpleRequest::SimpleRequest;
|
||||||
@@ -3090,8 +3038,7 @@ private:
|
|||||||
friend SimpleRequest;
|
friend SimpleRequest;
|
||||||
|
|
||||||
// Evaluation.
|
// Evaluation.
|
||||||
ResultBuilderBodyPreCheck
|
bool evaluate(Evaluator &evaluator, const BraceStmt *BS) const;
|
||||||
evaluate(Evaluator &evaluator, PreCheckResultBuilderDescriptor owner) const;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// Separate caching.
|
// Separate caching.
|
||||||
|
|||||||
@@ -381,8 +381,8 @@ SWIFT_REQUEST(TypeChecker, HasUserDefinedDesignatedInitRequest,
|
|||||||
bool(NominalTypeDecl *), Cached, NoLocationInfo)
|
bool(NominalTypeDecl *), Cached, NoLocationInfo)
|
||||||
SWIFT_REQUEST(TypeChecker, HasMemberwiseInitRequest,
|
SWIFT_REQUEST(TypeChecker, HasMemberwiseInitRequest,
|
||||||
bool(StructDecl *), Cached, NoLocationInfo)
|
bool(StructDecl *), Cached, NoLocationInfo)
|
||||||
SWIFT_REQUEST(TypeChecker, PreCheckResultBuilderRequest,
|
SWIFT_REQUEST(TypeChecker, BraceHasReturnRequest,
|
||||||
ResultBuilderBodyPreCheck(PreCheckResultBuilderDescriptor),
|
bool(const BraceStmt *),
|
||||||
Cached, NoLocationInfo)
|
Cached, NoLocationInfo)
|
||||||
SWIFT_REQUEST(TypeChecker, ResolveImplicitMemberRequest,
|
SWIFT_REQUEST(TypeChecker, ResolveImplicitMemberRequest,
|
||||||
evaluator::SideEffect(NominalTypeDecl *, ImplicitMemberAction),
|
evaluator::SideEffect(NominalTypeDecl *, ImplicitMemberAction),
|
||||||
|
|||||||
@@ -1317,25 +1317,6 @@ void AssociatedConformanceRequest::cacheResult(
|
|||||||
conformance->setAssociatedConformance(index, assocConf);
|
conformance->setAssociatedConformance(index, assocConf);
|
||||||
}
|
}
|
||||||
|
|
||||||
//----------------------------------------------------------------------------//
|
|
||||||
// PreCheckResultBuilderRequest computation.
|
|
||||||
//----------------------------------------------------------------------------//
|
|
||||||
|
|
||||||
void swift::simple_display(llvm::raw_ostream &out,
|
|
||||||
ResultBuilderBodyPreCheck value) {
|
|
||||||
switch (value) {
|
|
||||||
case ResultBuilderBodyPreCheck::Okay:
|
|
||||||
out << "okay";
|
|
||||||
break;
|
|
||||||
case ResultBuilderBodyPreCheck::HasReturnStmt:
|
|
||||||
out << "has return statement";
|
|
||||||
break;
|
|
||||||
case ResultBuilderBodyPreCheck::Error:
|
|
||||||
out << "error";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//----------------------------------------------------------------------------//
|
//----------------------------------------------------------------------------//
|
||||||
// HasCircularInheritedProtocolsRequest computation.
|
// HasCircularInheritedProtocolsRequest computation.
|
||||||
//----------------------------------------------------------------------------//
|
//----------------------------------------------------------------------------//
|
||||||
|
|||||||
@@ -920,23 +920,10 @@ private:
|
|||||||
|
|
||||||
std::optional<BraceStmt *>
|
std::optional<BraceStmt *>
|
||||||
TypeChecker::applyResultBuilderBodyTransform(FuncDecl *func, Type builderType) {
|
TypeChecker::applyResultBuilderBodyTransform(FuncDecl *func, Type builderType) {
|
||||||
// Pre-check the body: pre-check any expressions in it and look
|
// First look for any return statements, and bail if we have any.
|
||||||
// for return statements.
|
|
||||||
//
|
|
||||||
// If we encountered an error or there was an explicit result type,
|
|
||||||
// bail out and report that to the caller.
|
|
||||||
auto &ctx = func->getASTContext();
|
auto &ctx = func->getASTContext();
|
||||||
auto request = PreCheckResultBuilderRequest{AnyFunctionRef(func)};
|
if (evaluateOrDefault(ctx.evaluator, BraceHasReturnRequest{func->getBody()},
|
||||||
switch (evaluateOrDefault(ctx.evaluator, request,
|
false)) {
|
||||||
ResultBuilderBodyPreCheck::Error)) {
|
|
||||||
case ResultBuilderBodyPreCheck::Okay:
|
|
||||||
// If the pre-check was okay, apply the result-builder transform.
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ResultBuilderBodyPreCheck::Error:
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
case ResultBuilderBodyPreCheck::HasReturnStmt: {
|
|
||||||
// One or more explicit 'return' statements were encountered, which
|
// One or more explicit 'return' statements were encountered, which
|
||||||
// disables the result builder transform. Warn when we do this.
|
// disables the result builder transform. Warn when we do this.
|
||||||
auto returnStmts = findReturnStatements(func);
|
auto returnStmts = findReturnStatements(func);
|
||||||
@@ -970,7 +957,10 @@ TypeChecker::applyResultBuilderBodyTransform(FuncDecl *func, Type builderType) {
|
|||||||
|
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
auto target = SyntacticElementTarget(func);
|
||||||
|
if (ConstraintSystem::preCheckTarget(target))
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
ConstraintSystemOptions options = ConstraintSystemFlags::AllowFixes;
|
ConstraintSystemOptions options = ConstraintSystemFlags::AllowFixes;
|
||||||
auto resultInterfaceTy = func->getResultInterfaceType();
|
auto resultInterfaceTy = func->getResultInterfaceType();
|
||||||
@@ -1018,8 +1008,7 @@ TypeChecker::applyResultBuilderBodyTransform(FuncDecl *func, Type builderType) {
|
|||||||
cs.Options |= ConstraintSystemFlags::ForCodeCompletion;
|
cs.Options |= ConstraintSystemFlags::ForCodeCompletion;
|
||||||
cs.solveForCodeCompletion(solutions);
|
cs.solveForCodeCompletion(solutions);
|
||||||
|
|
||||||
SyntacticElementTarget funcTarget(func);
|
CompletionContextFinder analyzer(target, func->getDeclContext());
|
||||||
CompletionContextFinder analyzer(funcTarget, func->getDeclContext());
|
|
||||||
if (analyzer.hasCompletion()) {
|
if (analyzer.hasCompletion()) {
|
||||||
filterSolutionsForCodeCompletion(solutions, analyzer);
|
filterSolutionsForCodeCompletion(solutions, analyzer);
|
||||||
for (const auto &solution : solutions) {
|
for (const auto &solution : solutions) {
|
||||||
@@ -1066,7 +1055,7 @@ TypeChecker::applyResultBuilderBodyTransform(FuncDecl *func, Type builderType) {
|
|||||||
|
|
||||||
case SolutionResult::Kind::UndiagnosedError:
|
case SolutionResult::Kind::UndiagnosedError:
|
||||||
reportSolutionsToSolutionCallback(salvagedResult);
|
reportSolutionsToSolutionCallback(salvagedResult);
|
||||||
cs.diagnoseFailureFor(SyntacticElementTarget(func));
|
cs.diagnoseFailureFor(target);
|
||||||
salvagedResult.markAsDiagnosed();
|
salvagedResult.markAsDiagnosed();
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
@@ -1100,8 +1089,7 @@ TypeChecker::applyResultBuilderBodyTransform(FuncDecl *func, Type builderType) {
|
|||||||
cs.applySolution(solutions.front());
|
cs.applySolution(solutions.front());
|
||||||
|
|
||||||
// Apply the solution to the function body.
|
// Apply the solution to the function body.
|
||||||
if (auto result =
|
if (auto result = cs.applySolution(solutions.front(), target)) {
|
||||||
cs.applySolution(solutions.front(), SyntacticElementTarget(func))) {
|
|
||||||
performSyntacticDiagnosticsForTarget(*result, /*isExprStmt*/ false);
|
performSyntacticDiagnosticsForTarget(*result, /*isExprStmt*/ false);
|
||||||
auto *body = result->getFunctionBody();
|
auto *body = result->getFunctionBody();
|
||||||
|
|
||||||
@@ -1142,21 +1130,8 @@ ConstraintSystem::matchResultBuilder(AnyFunctionRef fn, Type builderType,
|
|||||||
// not apply the result builder transform if it contained an explicit return.
|
// not apply the result builder transform if it contained an explicit return.
|
||||||
// To maintain source compatibility, we still need to check for HasReturnStmt.
|
// To maintain source compatibility, we still need to check for HasReturnStmt.
|
||||||
// https://github.com/apple/swift/issues/64332.
|
// https://github.com/apple/swift/issues/64332.
|
||||||
switch (evaluateOrDefault(getASTContext().evaluator,
|
if (evaluateOrDefault(getASTContext().evaluator,
|
||||||
PreCheckResultBuilderRequest{fn},
|
BraceHasReturnRequest{fn.getBody()}, false)) {
|
||||||
ResultBuilderBodyPreCheck::Error)) {
|
|
||||||
case ResultBuilderBodyPreCheck::Okay:
|
|
||||||
// If the pre-check was okay, apply the result-builder transform.
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ResultBuilderBodyPreCheck::Error: {
|
|
||||||
llvm_unreachable(
|
|
||||||
"Running PreCheckResultBuilderRequest on a function shouldn't run "
|
|
||||||
"preCheckExpression and thus we should never enter this case.");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case ResultBuilderBodyPreCheck::HasReturnStmt:
|
|
||||||
// Diagnostic mode means that solver couldn't reach any viable
|
// Diagnostic mode means that solver couldn't reach any viable
|
||||||
// solution, so let's diagnose presence of a `return` statement
|
// solution, so let's diagnose presence of a `return` statement
|
||||||
// in the closure body.
|
// in the closure body.
|
||||||
@@ -1257,42 +1232,14 @@ ConstraintSystem::matchResultBuilder(AnyFunctionRef fn, Type builderType,
|
|||||||
}
|
}
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
class ReturnStmtFinder : public ASTWalker {
|
||||||
/// Pre-check all the expressions in the body.
|
|
||||||
class PreCheckResultBuilderApplication : public ASTWalker {
|
|
||||||
AnyFunctionRef Fn;
|
|
||||||
bool SkipPrecheck = false;
|
|
||||||
bool SuppressDiagnostics = false;
|
|
||||||
std::vector<ReturnStmt *> ReturnStmts;
|
std::vector<ReturnStmt *> ReturnStmts;
|
||||||
bool HasError = false;
|
|
||||||
|
|
||||||
bool hasReturnStmt() const { return !ReturnStmts.empty(); }
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
PreCheckResultBuilderApplication(AnyFunctionRef fn, bool skipPrecheck,
|
static std::vector<ReturnStmt *> find(const BraceStmt *BS) {
|
||||||
bool suppressDiagnostics)
|
ReturnStmtFinder finder;
|
||||||
: Fn(fn), SkipPrecheck(skipPrecheck),
|
const_cast<BraceStmt *>(BS)->walk(finder);
|
||||||
SuppressDiagnostics(suppressDiagnostics) {}
|
return std::move(finder.ReturnStmts);
|
||||||
|
|
||||||
const std::vector<ReturnStmt *> getReturnStmts() const { return ReturnStmts; }
|
|
||||||
|
|
||||||
ResultBuilderBodyPreCheck run() {
|
|
||||||
Stmt *oldBody = Fn.getBody();
|
|
||||||
|
|
||||||
Stmt *newBody = oldBody->walk(*this);
|
|
||||||
|
|
||||||
// If the walk was aborted, it was because we had a problem of some kind.
|
|
||||||
assert((newBody == nullptr) == HasError &&
|
|
||||||
"unexpected short-circuit while walking body");
|
|
||||||
if (HasError)
|
|
||||||
return ResultBuilderBodyPreCheck::Error;
|
|
||||||
|
|
||||||
assert(oldBody == newBody && "pre-check walk wasn't in-place?");
|
|
||||||
|
|
||||||
if (hasReturnStmt())
|
|
||||||
return ResultBuilderBodyPreCheck::HasReturnStmt;
|
|
||||||
|
|
||||||
return ResultBuilderBodyPreCheck::Okay;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MacroWalking getMacroWalkingBehavior() const override {
|
MacroWalking getMacroWalkingBehavior() const override {
|
||||||
@@ -1300,70 +1247,17 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
PreWalkResult<Expr *> walkToExprPre(Expr *E) override {
|
PreWalkResult<Expr *> walkToExprPre(Expr *E) override {
|
||||||
if (SkipPrecheck)
|
return Action::SkipNode(E);
|
||||||
return Action::SkipNode(E);
|
|
||||||
|
|
||||||
// Pre-check the expression. If this fails, abort the walk immediately.
|
|
||||||
// Otherwise, replace the expression with the result of pre-checking.
|
|
||||||
// In either case, don't recurse into the expression.
|
|
||||||
{
|
|
||||||
auto *DC = Fn.getAsDeclContext();
|
|
||||||
auto &diagEngine = DC->getASTContext().Diags;
|
|
||||||
|
|
||||||
// Suppress any diagnostics which could be produced by this expression.
|
|
||||||
DiagnosticTransaction transaction(diagEngine);
|
|
||||||
|
|
||||||
HasError |= ConstraintSystem::preCheckExpression(E, DC);
|
|
||||||
|
|
||||||
HasError |= transaction.hasErrors();
|
|
||||||
|
|
||||||
if (!HasError)
|
|
||||||
HasError |= containsErrorExpr(E);
|
|
||||||
|
|
||||||
if (SuppressDiagnostics)
|
|
||||||
transaction.abort();
|
|
||||||
|
|
||||||
if (HasError)
|
|
||||||
return Action::Stop();
|
|
||||||
|
|
||||||
return Action::SkipNode(E);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PreWalkResult<Stmt *> walkToStmtPre(Stmt *S) override {
|
PreWalkResult<Stmt *> walkToStmtPre(Stmt *S) override {
|
||||||
// If we see a return statement, note it..
|
// If we see a return statement, note it..
|
||||||
if (auto returnStmt = dyn_cast<ReturnStmt>(S)) {
|
auto *returnStmt = dyn_cast<ReturnStmt>(S);
|
||||||
if (!returnStmt->isImplicit()) {
|
if (!returnStmt || returnStmt->isImplicit())
|
||||||
ReturnStmts.push_back(returnStmt);
|
return Action::Continue(S);
|
||||||
return Action::SkipNode(S);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, recurse into the statement normally.
|
ReturnStmts.push_back(returnStmt);
|
||||||
return Action::Continue(S);
|
return Action::SkipNode(S);
|
||||||
}
|
|
||||||
|
|
||||||
/// Check whether given expression (including single-statement
|
|
||||||
/// closures) contains `ErrorExpr` as one of its sub-expressions.
|
|
||||||
bool containsErrorExpr(Expr *expr) {
|
|
||||||
bool hasError = false;
|
|
||||||
|
|
||||||
expr->forEachChildExpr([&](Expr *expr) -> Expr * {
|
|
||||||
hasError |= isa<ErrorExpr>(expr);
|
|
||||||
if (hasError)
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
if (auto *closure = dyn_cast<ClosureExpr>(expr)) {
|
|
||||||
if (closure->hasSingleExpressionBody()) {
|
|
||||||
hasError |= containsErrorExpr(closure->getSingleExpressionBody());
|
|
||||||
return hasError ? nullptr : expr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return expr;
|
|
||||||
});
|
|
||||||
|
|
||||||
return hasError;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ignore patterns.
|
/// Ignore patterns.
|
||||||
@@ -1371,24 +1265,15 @@ public:
|
|||||||
return Action::SkipNode(pat);
|
return Action::SkipNode(pat);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
} // end anonymous namespace
|
||||||
|
|
||||||
}
|
bool BraceHasReturnRequest::evaluate(Evaluator &evaluator,
|
||||||
|
const BraceStmt *BS) const {
|
||||||
ResultBuilderBodyPreCheck PreCheckResultBuilderRequest::evaluate(
|
return !ReturnStmtFinder::find(BS).empty();
|
||||||
Evaluator &evaluator, PreCheckResultBuilderDescriptor owner) const {
|
|
||||||
// Closures should already be pre-checked when we run this, so there's no need
|
|
||||||
// to pre-check them again.
|
|
||||||
bool skipPrecheck = owner.Fn.getAbstractClosureExpr();
|
|
||||||
return PreCheckResultBuilderApplication(
|
|
||||||
owner.Fn, skipPrecheck, /*suppressDiagnostics=*/false)
|
|
||||||
.run();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<ReturnStmt *> TypeChecker::findReturnStatements(AnyFunctionRef fn) {
|
std::vector<ReturnStmt *> TypeChecker::findReturnStatements(AnyFunctionRef fn) {
|
||||||
PreCheckResultBuilderApplication precheck(fn, /*skipPreCheck=*/true,
|
return ReturnStmtFinder::find(fn.getBody());
|
||||||
/*SuppressDiagnostics=*/true);
|
|
||||||
(void)precheck.run();
|
|
||||||
return precheck.getReturnStmts();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultBuilderOpSupport TypeChecker::checkBuilderOpSupport(
|
ResultBuilderOpSupport TypeChecker::checkBuilderOpSupport(
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ struct TupleBuilderWithoutIf { // expected-note 3{{struct 'TupleBuilderWithoutIf
|
|||||||
static func buildDo<T>(_ value: T) -> T { return value }
|
static func buildDo<T>(_ value: T) -> T { return value }
|
||||||
}
|
}
|
||||||
|
|
||||||
func tuplify<T>(_ cond: Bool, @TupleBuilder body: (Bool) -> T) { // expected-note {{'tuplify(_:body:)' declared here}}
|
func tuplify<T>(_ cond: Bool, @TupleBuilder body: (Bool) -> T) { // expected-note 2{{'tuplify(_:body:)' declared here}}
|
||||||
print(body(cond))
|
print(body(cond))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -455,7 +455,7 @@ func testNonExhaustiveSwitch(e: E) {
|
|||||||
// rdar://problem/59856491
|
// rdar://problem/59856491
|
||||||
struct TestConstraintGenerationErrors {
|
struct TestConstraintGenerationErrors {
|
||||||
@TupleBuilder var buildTupleFnBody: String {
|
@TupleBuilder var buildTupleFnBody: String {
|
||||||
let a = nil // There is no diagnostic here because next line fails to pre-check, so body is invalid
|
let a = nil // expected-error {{'nil' requires a contextual type}}
|
||||||
String(nothing) // expected-error {{cannot find 'nothing' in scope}}
|
String(nothing) // expected-error {{cannot find 'nothing' in scope}}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -722,7 +722,7 @@ struct TuplifiedStructWithInvalidClosure {
|
|||||||
|
|
||||||
@TupleBuilder var errorsDiagnosedByParser: some Any {
|
@TupleBuilder var errorsDiagnosedByParser: some Any {
|
||||||
if let _ = condition {
|
if let _ = condition {
|
||||||
tuplify { _ in
|
tuplify { _ in // expected-error {{missing argument for parameter #1 in call}}
|
||||||
self. // expected-error {{expected member name following '.'}}
|
self. // expected-error {{expected member name following '.'}}
|
||||||
}
|
}
|
||||||
42
|
42
|
||||||
|
|||||||
Reference in New Issue
Block a user