mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
Merge pull request #27682 from ChristopherRogers/master
[ClangImporter] Fix edge cases where custom name matches native name
This commit is contained in:
@@ -1354,7 +1354,7 @@ bool ClangImporter::importHeader(StringRef header, ModuleDecl *adapter,
|
|||||||
|
|
||||||
// If we've made it to here, this is some header other than the bridging
|
// If we've made it to here, this is some header other than the bridging
|
||||||
// header, which means we can no longer rely on one file's modification time
|
// header, which means we can no longer rely on one file's modification time
|
||||||
// to invalid code completion caches. :-(
|
// to invalidate code completion caches. :-(
|
||||||
Impl.setSinglePCHImport(None);
|
Impl.setSinglePCHImport(None);
|
||||||
|
|
||||||
if (!cachedContents.empty() && cachedContents.back() == '\0')
|
if (!cachedContents.empty() && cachedContents.back() == '\0')
|
||||||
@@ -3702,8 +3702,16 @@ EffectiveClangContext ClangImporter::Implementation::getEffectiveClangContext(
|
|||||||
(nominal->getAttrs().hasAttribute<ObjCAttr>() ||
|
(nominal->getAttrs().hasAttribute<ObjCAttr>() ||
|
||||||
(!nominal->getParentSourceFile() && nominal->isObjC()))) {
|
(!nominal->getParentSourceFile() && nominal->isObjC()))) {
|
||||||
// Map the name. If we can't represent the Swift name in Clang.
|
// Map the name. If we can't represent the Swift name in Clang.
|
||||||
// FIXME: We should be using the Objective-C name here!
|
Identifier name = nominal->getName();
|
||||||
auto clangName = exportName(nominal->getName());
|
if (auto objcAttr = nominal->getAttrs().getAttribute<ObjCAttr>()) {
|
||||||
|
if (auto objcName = objcAttr->getName()) {
|
||||||
|
if (objcName->getNumArgs() == 0) {
|
||||||
|
// This is an error if not 0, but it should be caught later.
|
||||||
|
name = objcName->getSimpleName();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
auto clangName = exportName(name);
|
||||||
if (!clangName)
|
if (!clangName)
|
||||||
return EffectiveClangContext();
|
return EffectiveClangContext();
|
||||||
|
|
||||||
|
|||||||
@@ -4535,24 +4535,64 @@ namespace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <typename T, typename U>
|
template <typename T, typename U>
|
||||||
T *resolveSwiftDeclImpl(const U *decl, Identifier name, ModuleDecl *overlay) {
|
T *resolveSwiftDeclImpl(const U *decl, Identifier name,
|
||||||
|
bool hasKnownSwiftName, ModuleDecl *overlay) {
|
||||||
const auto &languageVersion =
|
const auto &languageVersion =
|
||||||
Impl.SwiftContext.LangOpts.EffectiveLanguageVersion;
|
Impl.SwiftContext.LangOpts.EffectiveLanguageVersion;
|
||||||
|
|
||||||
|
auto isMatch = [&](const T *singleResult, bool baseNameMatches) -> bool {
|
||||||
|
const DeclAttributes &attrs = singleResult->getAttrs();
|
||||||
|
|
||||||
|
// Skip versioned variants.
|
||||||
|
if (attrs.isUnavailableInSwiftVersion(languageVersion))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Skip if type not exposed to Objective-C.
|
||||||
|
// If the base name doesn't match, then a matching
|
||||||
|
// custom name in an @objc attribute is required.
|
||||||
|
if (baseNameMatches && !singleResult->isObjC())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// If Clang decl has a custom Swift name, then we know that
|
||||||
|
// `name` is the base name we're looking for.
|
||||||
|
if (hasKnownSwiftName)
|
||||||
|
return baseNameMatches;
|
||||||
|
|
||||||
|
// Skip if a different name is used for Objective-C.
|
||||||
|
if (auto objcAttr = attrs.getAttribute<ObjCAttr>())
|
||||||
|
if (auto objcName = objcAttr->getName())
|
||||||
|
return objcName->getSimpleName() == name;
|
||||||
|
|
||||||
|
return baseNameMatches;
|
||||||
|
};
|
||||||
|
|
||||||
|
// First look at Swift types with the same name.
|
||||||
SmallVector<ValueDecl *, 4> results;
|
SmallVector<ValueDecl *, 4> results;
|
||||||
overlay->lookupValue(name, NLKind::QualifiedLookup, results);
|
overlay->lookupValue(name, NLKind::QualifiedLookup, results);
|
||||||
T *found = nullptr;
|
T *found = nullptr;
|
||||||
for (auto result : results) {
|
for (auto result : results) {
|
||||||
if (auto singleResult = dyn_cast<T>(result)) {
|
if (auto singleResult = dyn_cast<T>(result)) {
|
||||||
// Skip versioned variants.
|
if (isMatch(singleResult, /*baseNameMatches=*/true)) {
|
||||||
const DeclAttributes &attrs = singleResult->getAttrs();
|
if (found)
|
||||||
if (attrs.isUnavailableInSwiftVersion(languageVersion))
|
return nullptr;
|
||||||
continue;
|
found = singleResult;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (found)
|
if (!found && !hasKnownSwiftName) {
|
||||||
return nullptr;
|
// Try harder to find a match looking at just custom Objective-C names.
|
||||||
|
SmallVector<Decl *, 64> results;
|
||||||
found = singleResult;
|
overlay->getTopLevelDecls(results);
|
||||||
|
for (auto result : results) {
|
||||||
|
if (auto singleResult = dyn_cast<T>(result)) {
|
||||||
|
// The base name _could_ match but it's irrelevant here.
|
||||||
|
if (isMatch(singleResult, /*baseNameMatches=*/false)) {
|
||||||
|
if (found)
|
||||||
|
return nullptr;
|
||||||
|
found = singleResult;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4565,15 +4605,16 @@ namespace {
|
|||||||
|
|
||||||
template <typename T, typename U>
|
template <typename T, typename U>
|
||||||
T *resolveSwiftDecl(const U *decl, Identifier name,
|
T *resolveSwiftDecl(const U *decl, Identifier name,
|
||||||
ClangModuleUnit *clangModule) {
|
bool hasKnownSwiftName, ClangModuleUnit *clangModule) {
|
||||||
if (auto overlay = clangModule->getOverlayModule())
|
if (auto overlay = clangModule->getOverlayModule())
|
||||||
return resolveSwiftDeclImpl<T>(decl, name, overlay);
|
return resolveSwiftDeclImpl<T>(decl, name, hasKnownSwiftName, overlay);
|
||||||
if (clangModule == Impl.ImportedHeaderUnit) {
|
if (clangModule == Impl.ImportedHeaderUnit) {
|
||||||
// Use an index-based loop because new owners can come in as we're
|
// Use an index-based loop because new owners can come in as we're
|
||||||
// iterating.
|
// iterating.
|
||||||
for (size_t i = 0; i < Impl.ImportedHeaderOwners.size(); ++i) {
|
for (size_t i = 0; i < Impl.ImportedHeaderOwners.size(); ++i) {
|
||||||
ModuleDecl *owner = Impl.ImportedHeaderOwners[i];
|
ModuleDecl *owner = Impl.ImportedHeaderOwners[i];
|
||||||
if (T *result = resolveSwiftDeclImpl<T>(decl, name, owner))
|
if (T *result = resolveSwiftDeclImpl<T>(decl, name,
|
||||||
|
hasKnownSwiftName, owner))
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4586,7 +4627,8 @@ namespace {
|
|||||||
if (!importer::hasNativeSwiftDecl(decl))
|
if (!importer::hasNativeSwiftDecl(decl))
|
||||||
return false;
|
return false;
|
||||||
auto wrapperUnit = cast<ClangModuleUnit>(dc->getModuleScopeContext());
|
auto wrapperUnit = cast<ClangModuleUnit>(dc->getModuleScopeContext());
|
||||||
swiftDecl = resolveSwiftDecl<T>(decl, name, wrapperUnit);
|
swiftDecl = resolveSwiftDecl<T>(decl, name, /*hasCustomSwiftName=*/true,
|
||||||
|
wrapperUnit);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4615,6 +4657,7 @@ namespace {
|
|||||||
*correctSwiftName);
|
*correctSwiftName);
|
||||||
|
|
||||||
Identifier name = importedName.getDeclName().getBaseIdentifier();
|
Identifier name = importedName.getDeclName().getBaseIdentifier();
|
||||||
|
bool hasKnownSwiftName = importedName.hasCustomName();
|
||||||
|
|
||||||
// FIXME: Figure out how to deal with incomplete protocols, since that
|
// FIXME: Figure out how to deal with incomplete protocols, since that
|
||||||
// notion doesn't exist in Swift.
|
// notion doesn't exist in Swift.
|
||||||
@@ -4622,6 +4665,7 @@ namespace {
|
|||||||
// Check if this protocol is implemented in its overlay.
|
// Check if this protocol is implemented in its overlay.
|
||||||
if (auto clangModule = Impl.getClangModuleForDecl(decl, true))
|
if (auto clangModule = Impl.getClangModuleForDecl(decl, true))
|
||||||
if (auto native = resolveSwiftDecl<ProtocolDecl>(decl, name,
|
if (auto native = resolveSwiftDecl<ProtocolDecl>(decl, name,
|
||||||
|
hasKnownSwiftName,
|
||||||
clangModule))
|
clangModule))
|
||||||
return native;
|
return native;
|
||||||
|
|
||||||
@@ -4733,11 +4777,13 @@ namespace {
|
|||||||
*correctSwiftName);
|
*correctSwiftName);
|
||||||
|
|
||||||
auto name = importedName.getDeclName().getBaseIdentifier();
|
auto name = importedName.getDeclName().getBaseIdentifier();
|
||||||
|
bool hasKnownSwiftName = importedName.hasCustomName();
|
||||||
|
|
||||||
if (!decl->hasDefinition()) {
|
if (!decl->hasDefinition()) {
|
||||||
// Check if this class is implemented in its overlay.
|
// Check if this class is implemented in its overlay.
|
||||||
if (auto clangModule = Impl.getClangModuleForDecl(decl, true)) {
|
if (auto clangModule = Impl.getClangModuleForDecl(decl, true)) {
|
||||||
if (auto native = resolveSwiftDecl<ClassDecl>(decl, name,
|
if (auto native = resolveSwiftDecl<ClassDecl>(decl, name,
|
||||||
|
hasKnownSwiftName,
|
||||||
clangModule)) {
|
clangModule)) {
|
||||||
return native;
|
return native;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1781,9 +1781,11 @@ ImportedName NameImporter::importName(const clang::NamedDecl *decl,
|
|||||||
ImportNameVersion version,
|
ImportNameVersion version,
|
||||||
clang::DeclarationName givenName) {
|
clang::DeclarationName givenName) {
|
||||||
CacheKeyType key(decl, version);
|
CacheKeyType key(decl, version);
|
||||||
if (importNameCache.count(key) && !givenName) {
|
if (!givenName) {
|
||||||
++ImportNameNumCacheHits;
|
if (auto cachedRes = importNameCache[key]) {
|
||||||
return importNameCache[key];
|
++ImportNameNumCacheHits;
|
||||||
|
return cachedRes;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
++ImportNameNumCacheMisses;
|
++ImportNameNumCacheMisses;
|
||||||
auto res = importNameImpl(decl, version, givenName);
|
auto res = importNameImpl(decl, version, givenName);
|
||||||
|
|||||||
@@ -1393,7 +1393,7 @@ IsObjCRequest::evaluate(Evaluator &evaluator, ValueDecl *VD) const {
|
|||||||
// Classes can be @objc.
|
// Classes can be @objc.
|
||||||
|
|
||||||
// Protocols and enums can also be @objc, but this is covered by the
|
// Protocols and enums can also be @objc, but this is covered by the
|
||||||
// isObjC() check a the beginning.;
|
// isObjC() check at the beginning.
|
||||||
isObjC = shouldMarkAsObjC(VD, /*allowImplicit=*/false);
|
isObjC = shouldMarkAsObjC(VD, /*allowImplicit=*/false);
|
||||||
} else if (auto enumDecl = dyn_cast<EnumDecl>(VD)) {
|
} else if (auto enumDecl = dyn_cast<EnumDecl>(VD)) {
|
||||||
// Enums can be @objc so long as they have a raw type that is representable
|
// Enums can be @objc so long as they have a raw type that is representable
|
||||||
|
|||||||
@@ -68,6 +68,62 @@ SWIFT_PROTOCOL("SwiftProto")
|
|||||||
id<SwiftProtoWithCustomName> _Nonnull
|
id<SwiftProtoWithCustomName> _Nonnull
|
||||||
convertToProto(SwiftClassWithCustomName *_Nonnull obj);
|
convertToProto(SwiftClassWithCustomName *_Nonnull obj);
|
||||||
|
|
||||||
|
SWIFT_CLASS("ObjCClass")
|
||||||
|
__attribute__((objc_root_class))
|
||||||
|
@interface ObjCClass
|
||||||
|
@end
|
||||||
|
|
||||||
|
SWIFT_CLASS("ImplicitlyObjCClass")
|
||||||
|
@interface ImplicitlyObjCClass : ObjCClass
|
||||||
|
@end
|
||||||
|
void consumeImplicitlyObjCClass(ImplicitlyObjCClass *_Nonnull obj);
|
||||||
|
|
||||||
|
SWIFT_CLASS("ExplicitlyObjCClass")
|
||||||
|
__attribute__((objc_root_class))
|
||||||
|
@interface ExplicitlyObjCClass
|
||||||
|
@end
|
||||||
|
void consumeExplicitlyObjCClass(ExplicitlyObjCClass *_Nonnull obj);
|
||||||
|
|
||||||
|
SWIFT_CLASS_NAMED("HasSameCustomNameClass")
|
||||||
|
__attribute__((objc_root_class))
|
||||||
|
@interface HasSameCustomNameClass
|
||||||
|
@end
|
||||||
|
void consumeHasSameCustomNameClass(HasSameCustomNameClass *_Nonnull obj);
|
||||||
|
|
||||||
|
SWIFT_CLASS_NAMED("SwiftNativeTypeHasDifferentCustomNameClass")
|
||||||
|
__attribute__((objc_root_class))
|
||||||
|
@interface NativeTypeHasDifferentCustomNameClass
|
||||||
|
@end
|
||||||
|
SWIFT_CLASS_NAMED("NativeTypeHasDifferentCustomNameClass")
|
||||||
|
__attribute__((objc_root_class))
|
||||||
|
@interface ObjCNativeTypeHasDifferentCustomNameClass
|
||||||
|
@end
|
||||||
|
void consumeNativeTypeHasDifferentCustomNameClass(NativeTypeHasDifferentCustomNameClass *_Nonnull obj);
|
||||||
|
void consumeObjCNativeTypeHasDifferentCustomNameClass(ObjCNativeTypeHasDifferentCustomNameClass *_Nonnull obj);
|
||||||
|
|
||||||
|
SWIFT_CLASS_NAMED("SwiftNativeTypeIsNonObjCClass")
|
||||||
|
__attribute__((objc_root_class))
|
||||||
|
@interface NativeTypeIsNonObjCClass
|
||||||
|
@end
|
||||||
|
void consumeNativeTypeIsNonObjCClass(NativeTypeIsNonObjCClass *_Nonnull obj);
|
||||||
|
|
||||||
|
@class ForwardImplicitlyObjCClass;
|
||||||
|
void consumeForwardImplicitlyObjCClass(ForwardImplicitlyObjCClass *_Nonnull obj);
|
||||||
|
|
||||||
|
@class ForwardExplicitlyObjCClass;
|
||||||
|
void consumeForwardExplicitlyObjCClass(ForwardExplicitlyObjCClass *_Nonnull obj);
|
||||||
|
|
||||||
|
@class ForwardHasSameCustomNameClass;
|
||||||
|
void consumeForwardHasSameCustomNameClass(ForwardHasSameCustomNameClass *_Nonnull obj);
|
||||||
|
|
||||||
|
@class ForwardNativeTypeHasDifferentCustomNameClass;
|
||||||
|
@class ObjCForwardNativeTypeHasDifferentCustomNameClass;
|
||||||
|
void consumeForwardNativeTypeHasDifferentCustomNameClass(ForwardNativeTypeHasDifferentCustomNameClass *_Nonnull obj);
|
||||||
|
void consumeObjCForwardNativeTypeHasDifferentCustomNameClass(ObjCForwardNativeTypeHasDifferentCustomNameClass *_Nonnull obj);
|
||||||
|
|
||||||
|
@class ForwardNativeTypeIsNonObjCClass;
|
||||||
|
void consumeForwardNativeTypeIsNonObjCClass(ForwardNativeTypeIsNonObjCClass *_Nonnull obj);
|
||||||
|
|
||||||
SWIFT_CLASS("BOGUS")
|
SWIFT_CLASS("BOGUS")
|
||||||
@interface BogusClass
|
@interface BogusClass
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -31,3 +31,70 @@ public class CustomNameClass : CustomName {
|
|||||||
@objc func protoMethod()
|
@objc func protoMethod()
|
||||||
@objc var protoProperty: Int { get }
|
@objc var protoProperty: Int { get }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc
|
||||||
|
public class ObjCClass {
|
||||||
|
public init() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ImplicitlyObjCClass : ObjCClass {
|
||||||
|
public override init() { super.init() }
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc
|
||||||
|
public class ExplicitlyObjCClass {
|
||||||
|
public init() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc(HasSameCustomNameClass)
|
||||||
|
public class HasSameCustomNameClass {
|
||||||
|
public init() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc(ObjCNativeTypeHasDifferentCustomNameClass)
|
||||||
|
public class NativeTypeHasDifferentCustomNameClass {
|
||||||
|
public init() {}
|
||||||
|
}
|
||||||
|
@objc(NativeTypeHasDifferentCustomNameClass)
|
||||||
|
public class SwiftNativeTypeHasDifferentCustomNameClass {
|
||||||
|
public init() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class NativeTypeIsNonObjCClass {
|
||||||
|
public init() {}
|
||||||
|
}
|
||||||
|
@objc(NativeTypeIsNonObjCClass)
|
||||||
|
public class SwiftNativeTypeIsNonObjCClass {
|
||||||
|
public init() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ForwardImplicitlyObjCClass : ObjCClass {
|
||||||
|
public override init() { super.init() }
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc
|
||||||
|
public class ForwardExplicitlyObjCClass {
|
||||||
|
public init() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc(ForwardHasSameCustomNameClass)
|
||||||
|
public class ForwardHasSameCustomNameClass {
|
||||||
|
public init() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc(ObjCForwardNativeTypeHasDifferentCustomNameClass)
|
||||||
|
public class ForwardNativeTypeHasDifferentCustomNameClass {
|
||||||
|
public init() {}
|
||||||
|
}
|
||||||
|
@objc(ForwardNativeTypeHasDifferentCustomNameClass)
|
||||||
|
public class SwiftForwardNativeTypeHasDifferentCustomNameClass {
|
||||||
|
public init() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ForwardNativeTypeIsNonObjCClass {
|
||||||
|
public init() {}
|
||||||
|
}
|
||||||
|
@objc(ForwardNativeTypeIsNonObjCClass)
|
||||||
|
public class SwiftForwardNativeTypeIsNonObjCClass {
|
||||||
|
public init() {}
|
||||||
|
}
|
||||||
|
|||||||
@@ -32,3 +32,31 @@ func testAnyObject(_ obj: AnyObject) {
|
|||||||
obj.protoMethod()
|
obj.protoMethod()
|
||||||
_ = obj.protoProperty
|
_ = obj.protoProperty
|
||||||
}
|
}
|
||||||
|
|
||||||
|
consumeImplicitlyObjCClass(ImplicitlyObjCClass())
|
||||||
|
|
||||||
|
consumeExplicitlyObjCClass(ExplicitlyObjCClass())
|
||||||
|
|
||||||
|
consumeHasSameCustomNameClass(HasSameCustomNameClass())
|
||||||
|
|
||||||
|
consumeNativeTypeHasDifferentCustomNameClass(SwiftNativeTypeHasDifferentCustomNameClass())
|
||||||
|
consumeObjCNativeTypeHasDifferentCustomNameClass(NativeTypeHasDifferentCustomNameClass())
|
||||||
|
consumeNativeTypeHasDifferentCustomNameClass(NativeTypeHasDifferentCustomNameClass()) // expected-error {{cannot convert value of type 'NativeTypeHasDifferentCustomNameClass' to expected argument type 'SwiftNativeTypeHasDifferentCustomNameClass'}}
|
||||||
|
consumeObjCNativeTypeHasDifferentCustomNameClass(SwiftNativeTypeHasDifferentCustomNameClass()) // expected-error {{cannot convert value of type 'SwiftNativeTypeHasDifferentCustomNameClass' to expected argument type 'NativeTypeHasDifferentCustomNameClass'}}
|
||||||
|
|
||||||
|
consumeNativeTypeIsNonObjCClass(SwiftNativeTypeIsNonObjCClass())
|
||||||
|
consumeNativeTypeIsNonObjCClass(NativeTypeIsNonObjCClass()) // expected-error {{cannot convert value of type 'NativeTypeIsNonObjCClass' to expected argument type 'SwiftNativeTypeIsNonObjCClass'}}
|
||||||
|
|
||||||
|
consumeForwardImplicitlyObjCClass(ForwardImplicitlyObjCClass())
|
||||||
|
|
||||||
|
consumeForwardExplicitlyObjCClass(ForwardExplicitlyObjCClass())
|
||||||
|
|
||||||
|
consumeForwardHasSameCustomNameClass(ForwardHasSameCustomNameClass())
|
||||||
|
|
||||||
|
consumeForwardNativeTypeHasDifferentCustomNameClass(SwiftForwardNativeTypeHasDifferentCustomNameClass())
|
||||||
|
consumeObjCForwardNativeTypeHasDifferentCustomNameClass(ForwardNativeTypeHasDifferentCustomNameClass())
|
||||||
|
consumeForwardNativeTypeHasDifferentCustomNameClass(ForwardNativeTypeHasDifferentCustomNameClass()) // expected-error {{cannot convert value of type 'ForwardNativeTypeHasDifferentCustomNameClass' to expected argument type 'SwiftForwardNativeTypeHasDifferentCustomNameClass'}}
|
||||||
|
consumeObjCForwardNativeTypeHasDifferentCustomNameClass(SwiftForwardNativeTypeHasDifferentCustomNameClass()) // expected-error {{cannot convert value of type 'SwiftForwardNativeTypeHasDifferentCustomNameClass' to expected argument type 'ForwardNativeTypeHasDifferentCustomNameClass'}}
|
||||||
|
|
||||||
|
consumeForwardNativeTypeIsNonObjCClass(SwiftForwardNativeTypeIsNonObjCClass())
|
||||||
|
consumeForwardNativeTypeIsNonObjCClass(ForwardNativeTypeIsNonObjCClass()) // expected-error {{cannot convert value of type 'ForwardNativeTypeIsNonObjCClass' to expected argument type 'SwiftForwardNativeTypeIsNonObjCClass'}}
|
||||||
|
|||||||
Reference in New Issue
Block a user