JavaDescriptorResolver: resolve constructors lazily
This speeds up analyze a bit (profiler shown about 10% of time spent in constructor resolver during completion in KDoc).
This commit is contained in:
+71
@@ -0,0 +1,71 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2012 JetBrains s.r.o.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.jet.lang.resolve.java;
|
||||||
|
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.intellij.psi.PsiClass;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.jet.lang.descriptors.ClassKind;
|
||||||
|
import org.jetbrains.jet.lang.descriptors.ConstructorDescriptor;
|
||||||
|
import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor;
|
||||||
|
import org.jetbrains.jet.lang.descriptors.MutableClassDescriptorLite;
|
||||||
|
import org.jetbrains.jet.lang.resolve.lazy.LazyClassDescriptor;
|
||||||
|
import org.jetbrains.jet.lang.resolve.name.FqName;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Stepan Koltsov
|
||||||
|
*
|
||||||
|
* @see LazyClassDescriptor
|
||||||
|
*/
|
||||||
|
public class ClassDescriptorFromJvmBytecode extends MutableClassDescriptorLite {
|
||||||
|
@NotNull
|
||||||
|
private final JavaDescriptorResolver javaDescriptorResolver;
|
||||||
|
@NotNull
|
||||||
|
private final JavaDescriptorResolver.ResolverBinaryClassData resolverBinaryClassData;
|
||||||
|
|
||||||
|
|
||||||
|
// lazy
|
||||||
|
private Collection<ConstructorDescriptor> constructors;
|
||||||
|
|
||||||
|
public ClassDescriptorFromJvmBytecode(
|
||||||
|
@NotNull DeclarationDescriptor containingDeclaration, @NotNull ClassKind kind,
|
||||||
|
@NotNull PsiClass psiClass, @NotNull FqName fqName,
|
||||||
|
@NotNull JavaDescriptorResolver javaDescriptorResolver) {
|
||||||
|
super(containingDeclaration, kind);
|
||||||
|
this.javaDescriptorResolver = javaDescriptorResolver;
|
||||||
|
this.resolverBinaryClassData = new JavaDescriptorResolver.ResolverBinaryClassData(psiClass, fqName, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public JavaDescriptorResolver.ResolverBinaryClassData getResolverBinaryClassData() {
|
||||||
|
return resolverBinaryClassData;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public Collection<ConstructorDescriptor> getConstructors() {
|
||||||
|
if (constructors == null) {
|
||||||
|
this.constructors = javaDescriptorResolver.resolveConstructors(resolverBinaryClassData);
|
||||||
|
}
|
||||||
|
return constructors;
|
||||||
|
}
|
||||||
|
}
|
||||||
+49
-31
@@ -183,9 +183,9 @@ public class JavaDescriptorResolver implements DependencyClassByQualifiedNameRes
|
|||||||
|
|
||||||
/** Class with instance members */
|
/** Class with instance members */
|
||||||
static class ResolverBinaryClassData extends ResolverScopeData {
|
static class ResolverBinaryClassData extends ResolverScopeData {
|
||||||
private final MutableClassDescriptorLite classDescriptor;
|
private final ClassDescriptorFromJvmBytecode classDescriptor;
|
||||||
|
|
||||||
ResolverBinaryClassData(@NotNull PsiClass psiClass, @NotNull FqName fqName, @NotNull MutableClassDescriptorLite classDescriptor) {
|
ResolverBinaryClassData(@NotNull PsiClass psiClass, @NotNull FqName fqName, @NotNull ClassDescriptorFromJvmBytecode classDescriptor) {
|
||||||
super(psiClass, null, fqName, false, classDescriptor);
|
super(psiClass, null, fqName, false, classDescriptor);
|
||||||
this.classDescriptor = classDescriptor;
|
this.classDescriptor = classDescriptor;
|
||||||
}
|
}
|
||||||
@@ -360,7 +360,9 @@ public class JavaDescriptorResolver implements DependencyClassByQualifiedNameRes
|
|||||||
return classData;
|
return classData;
|
||||||
}
|
}
|
||||||
|
|
||||||
classData = new ResolverBinaryClassData(psiClass, fqName, new MutableClassDescriptorLite(containingDeclaration, kind));
|
classData = new ClassDescriptorFromJvmBytecode(
|
||||||
|
containingDeclaration, kind, psiClass, fqName, this)
|
||||||
|
.getResolverBinaryClassData();
|
||||||
classDescriptorCache.put(fqName, classData);
|
classDescriptorCache.put(fqName, classData);
|
||||||
classData.classDescriptor.setName(name);
|
classData.classDescriptor.setName(name);
|
||||||
classData.classDescriptor.setAnnotations(resolveAnnotations(psiClass, taskList));
|
classData.classDescriptor.setAnnotations(resolveAnnotations(psiClass, taskList));
|
||||||
@@ -392,17 +394,40 @@ public class JavaDescriptorResolver implements DependencyClassByQualifiedNameRes
|
|||||||
|
|
||||||
initializeTypeParameters(classData.typeParameters, classData.classDescriptor, "class " + psiClass.getQualifiedName());
|
initializeTypeParameters(classData.typeParameters, classData.classDescriptor, "class " + psiClass.getQualifiedName());
|
||||||
|
|
||||||
TypeVariableResolver resolverForTypeParameters = TypeVariableResolvers.classTypeVariableResolver(
|
|
||||||
classData.classDescriptor,
|
|
||||||
"class " + psiClass.getQualifiedName());
|
|
||||||
|
|
||||||
// TODO: ugly hack: tests crash if initializeTypeParameters called with class containing proper supertypes
|
// TODO: ugly hack: tests crash if initializeTypeParameters called with class containing proper supertypes
|
||||||
supertypes.addAll(getSupertypes(new PsiClassWrapper(psiClass), classData, classData.getTypeParameters()));
|
supertypes.addAll(getSupertypes(new PsiClassWrapper(psiClass), classData, classData.getTypeParameters()));
|
||||||
|
|
||||||
|
MutableClassDescriptorLite classObject = createClassObjectDescriptor(classData.classDescriptor, psiClass);
|
||||||
|
if (classObject != null) {
|
||||||
|
classData.classDescriptor.getBuilder().setClassObjectDescriptor(classObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
trace.record(BindingContext.CLASS, psiClass, classData.classDescriptor);
|
||||||
|
|
||||||
|
return classData;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public Collection<ConstructorDescriptor> resolveConstructors(@NotNull ResolverBinaryClassData classData) {
|
||||||
|
Collection<ConstructorDescriptor> constructors = Lists.newArrayList();
|
||||||
|
|
||||||
|
PsiClass psiClass = classData.psiClass;
|
||||||
|
|
||||||
|
TypeVariableResolver resolverForTypeParameters = TypeVariableResolvers.classTypeVariableResolver(
|
||||||
|
classData.classDescriptor, "class " + psiClass.getQualifiedName());
|
||||||
|
|
||||||
|
List<TypeParameterDescriptor> typeParameters = classData.classDescriptor.getTypeConstructor().getParameters();
|
||||||
|
|
||||||
PsiMethod[] psiConstructors = psiClass.getConstructors();
|
PsiMethod[] psiConstructors = psiClass.getConstructors();
|
||||||
|
|
||||||
boolean isStatic = psiClass.hasModifierProperty(PsiModifier.STATIC);
|
boolean isStatic = psiClass.hasModifierProperty(PsiModifier.STATIC);
|
||||||
if (psiConstructors.length == 0) {
|
if (classData.classDescriptor.getKind() == ClassKind.OBJECT) {
|
||||||
|
// TODO: wrong: class objects do not need visible constructors
|
||||||
|
ConstructorDescriptorImpl constructor = new ConstructorDescriptorImpl(classData.classDescriptor, new ArrayList<AnnotationDescriptor>(0), true);
|
||||||
|
constructor.initialize(new ArrayList<TypeParameterDescriptor>(0), new ArrayList<ValueParameterDescriptor>(0), Visibilities.PUBLIC);
|
||||||
|
constructors.add(constructor);
|
||||||
|
}
|
||||||
|
else if (psiConstructors.length == 0) {
|
||||||
// We need to create default constructors for classes and abstract classes.
|
// We need to create default constructors for classes and abstract classes.
|
||||||
// Example:
|
// Example:
|
||||||
// class Kotlin() : Java() {}
|
// class Kotlin() : Java() {}
|
||||||
@@ -413,8 +438,7 @@ public class JavaDescriptorResolver implements DependencyClassByQualifiedNameRes
|
|||||||
Collections.<AnnotationDescriptor>emptyList(),
|
Collections.<AnnotationDescriptor>emptyList(),
|
||||||
false);
|
false);
|
||||||
constructorDescriptor.initialize(typeParameters, Collections.<ValueParameterDescriptor>emptyList(), classData.classDescriptor.getVisibility(), isStatic);
|
constructorDescriptor.initialize(typeParameters, Collections.<ValueParameterDescriptor>emptyList(), classData.classDescriptor.getVisibility(), isStatic);
|
||||||
constructorDescriptor.setReturnType(classData.classDescriptor.getDefaultType());
|
constructors.add(constructorDescriptor);
|
||||||
classData.classDescriptor.addConstructor(constructorDescriptor, null);
|
|
||||||
trace.record(BindingContext.CONSTRUCTOR, psiClass, constructorDescriptor);
|
trace.record(BindingContext.CONSTRUCTOR, psiClass, constructorDescriptor);
|
||||||
}
|
}
|
||||||
if (psiClass.isAnnotationType()) {
|
if (psiClass.isAnnotationType()) {
|
||||||
@@ -454,32 +478,32 @@ public class JavaDescriptorResolver implements DependencyClassByQualifiedNameRes
|
|||||||
}
|
}
|
||||||
|
|
||||||
constructorDescriptor.initialize(typeParameters, valueParameters, classData.classDescriptor.getVisibility(), isStatic);
|
constructorDescriptor.initialize(typeParameters, valueParameters, classData.classDescriptor.getVisibility(), isStatic);
|
||||||
constructorDescriptor.setReturnType(classData.classDescriptor.getDefaultType());
|
constructors.add(constructorDescriptor);
|
||||||
classData.classDescriptor.addConstructor(constructorDescriptor, null);
|
|
||||||
trace.record(BindingContext.CONSTRUCTOR, psiClass, constructorDescriptor);
|
trace.record(BindingContext.CONSTRUCTOR, psiClass, constructorDescriptor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
for (PsiMethod psiConstructor : psiConstructors) {
|
for (PsiMethod psiConstructor : psiConstructors) {
|
||||||
resolveConstructor(psiClass, classData, isStatic, psiConstructor);
|
ConstructorDescriptor constructor = resolveConstructor(psiClass, classData, isStatic, psiConstructor);
|
||||||
|
if (constructor != null) {
|
||||||
|
constructors.add(constructor);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MutableClassDescriptorLite classObject = createClassObjectDescriptor(classData.classDescriptor, psiClass);
|
for (ConstructorDescriptor constructor : constructors) {
|
||||||
if (classObject != null) {
|
((ConstructorDescriptorImpl) constructor).setReturnType(classData.classDescriptor.getDefaultType());
|
||||||
classData.classDescriptor.getBuilder().setClassObjectDescriptor(classObject);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
trace.record(BindingContext.CLASS, psiClass, classData.classDescriptor);
|
return constructors;
|
||||||
|
|
||||||
return classData;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void resolveConstructor(PsiClass psiClass, ResolverBinaryClassData classData, boolean aStatic, PsiMethod psiConstructor) {
|
@Nullable
|
||||||
|
private ConstructorDescriptor resolveConstructor(PsiClass psiClass, ResolverBinaryClassData classData, boolean aStatic, PsiMethod psiConstructor) {
|
||||||
PsiMethodWrapper constructor = new PsiMethodWrapper(psiConstructor);
|
PsiMethodWrapper constructor = new PsiMethodWrapper(psiConstructor);
|
||||||
|
|
||||||
if (constructor.getJetConstructor().hidden()) {
|
if (constructor.getJetConstructor().hidden()) {
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
ConstructorDescriptorImpl constructorDescriptor = new ConstructorDescriptorImpl(
|
ConstructorDescriptorImpl constructorDescriptor = new ConstructorDescriptorImpl(
|
||||||
@@ -496,9 +520,8 @@ public class JavaDescriptorResolver implements DependencyClassByQualifiedNameRes
|
|||||||
constructorDescriptor.initialize(classData.classDescriptor.getTypeConstructor().getParameters(),
|
constructorDescriptor.initialize(classData.classDescriptor.getTypeConstructor().getParameters(),
|
||||||
valueParameterDescriptors.descriptors,
|
valueParameterDescriptors.descriptors,
|
||||||
resolveVisibilityFromPsiModifiers(psiConstructor), aStatic);
|
resolveVisibilityFromPsiModifiers(psiConstructor), aStatic);
|
||||||
constructorDescriptor.setReturnType(classData.classDescriptor.getDefaultType());
|
|
||||||
classData.classDescriptor.addConstructor(constructorDescriptor, null);
|
|
||||||
trace.record(BindingContext.CONSTRUCTOR, psiConstructor, constructorDescriptor);
|
trace.record(BindingContext.CONSTRUCTOR, psiConstructor, constructorDescriptor);
|
||||||
|
return constructorDescriptor;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void checkPsiClassIsNotJet(PsiClass psiClass) {
|
static void checkPsiClassIsNotJet(PsiClass psiClass) {
|
||||||
@@ -531,8 +554,9 @@ public class JavaDescriptorResolver implements DependencyClassByQualifiedNameRes
|
|||||||
checkPsiClassIsNotJet(psiClass);
|
checkPsiClassIsNotJet(psiClass);
|
||||||
|
|
||||||
FqName fqName = new FqName(classObjectPsiClass.getQualifiedName());
|
FqName fqName = new FqName(classObjectPsiClass.getQualifiedName());
|
||||||
ResolverBinaryClassData classData = new ResolverBinaryClassData(classObjectPsiClass, fqName,
|
ResolverBinaryClassData classData = new ClassDescriptorFromJvmBytecode(
|
||||||
new MutableClassDescriptorLite(containing, ClassKind.OBJECT));
|
containing, ClassKind.OBJECT, classObjectPsiClass, fqName, this)
|
||||||
|
.getResolverBinaryClassData();
|
||||||
|
|
||||||
classDescriptorCache.put(fqName, classData);
|
classDescriptorCache.put(fqName, classData);
|
||||||
|
|
||||||
@@ -544,12 +568,6 @@ public class JavaDescriptorResolver implements DependencyClassByQualifiedNameRes
|
|||||||
classData.classDescriptor.createTypeConstructor();
|
classData.classDescriptor.createTypeConstructor();
|
||||||
classData.classDescriptor.setScopeForMemberLookup(new JavaClassMembersScope(semanticServices, classData));
|
classData.classDescriptor.setScopeForMemberLookup(new JavaClassMembersScope(semanticServices, classData));
|
||||||
|
|
||||||
// TODO: wrong: class objects do not need visible constructors
|
|
||||||
ConstructorDescriptorImpl constructor = new ConstructorDescriptorImpl(classData.classDescriptor, new ArrayList<AnnotationDescriptor>(0), true);
|
|
||||||
constructor.setReturnType(classData.classDescriptor.getDefaultType());
|
|
||||||
constructor.initialize(new ArrayList<TypeParameterDescriptor>(0), new ArrayList<ValueParameterDescriptor>(0), Visibilities.PUBLIC);
|
|
||||||
|
|
||||||
classData.classDescriptor.addConstructor(constructor, null);
|
|
||||||
return classData.classDescriptor;
|
return classData.classDescriptor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+18
-2
@@ -38,6 +38,7 @@ import java.util.Set;
|
|||||||
* @author abreslav
|
* @author abreslav
|
||||||
*/
|
*/
|
||||||
public class MutableClassDescriptor extends MutableClassDescriptorLite implements ClassDescriptorFromSource {
|
public class MutableClassDescriptor extends MutableClassDescriptorLite implements ClassDescriptorFromSource {
|
||||||
|
private final Set<ConstructorDescriptor> constructors = Sets.newLinkedHashSet();
|
||||||
private ConstructorDescriptor primaryConstructor;
|
private ConstructorDescriptor primaryConstructor;
|
||||||
|
|
||||||
private final Set<CallableMemberDescriptor> declaredCallableMembers = Sets.newHashSet();
|
private final Set<CallableMemberDescriptor> declaredCallableMembers = Sets.newHashSet();
|
||||||
@@ -72,9 +73,15 @@ public class MutableClassDescriptor extends MutableClassDescriptorLite implement
|
|||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addConstructor(@NotNull ConstructorDescriptor constructorDescriptor, @NotNull BindingTrace trace) {
|
public void addConstructor(@NotNull ConstructorDescriptor constructorDescriptor, @NotNull BindingTrace trace) {
|
||||||
super.addConstructor(constructorDescriptor, trace);
|
if (constructorDescriptor.getContainingDeclaration() != this) {
|
||||||
|
throw new IllegalStateException("invalid containing declaration of constructor");
|
||||||
|
}
|
||||||
|
constructors.add(constructorDescriptor);
|
||||||
|
if (defaultType != null) {
|
||||||
|
((ConstructorDescriptorImpl) constructorDescriptor).setReturnType(getDefaultType());
|
||||||
|
}
|
||||||
|
|
||||||
if (constructorDescriptor.isPrimary()) {
|
if (constructorDescriptor.isPrimary()) {
|
||||||
setUpScopeForInitializers(constructorDescriptor);
|
setUpScopeForInitializers(constructorDescriptor);
|
||||||
for (ValueParameterDescriptor valueParameterDescriptor : constructorDescriptor.getValueParameters()) {
|
for (ValueParameterDescriptor valueParameterDescriptor : constructorDescriptor.getValueParameters()) {
|
||||||
@@ -93,6 +100,12 @@ public class MutableClassDescriptor extends MutableClassDescriptorLite implement
|
|||||||
addConstructor(constructorDescriptor, trace);
|
addConstructor(constructorDescriptor, trace);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public Set<ConstructorDescriptor> getConstructors() {
|
||||||
|
return constructors;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
public ConstructorDescriptor getUnsubstitutedPrimaryConstructor() {
|
public ConstructorDescriptor getUnsubstitutedPrimaryConstructor() {
|
||||||
@@ -141,6 +154,9 @@ public class MutableClassDescriptor extends MutableClassDescriptorLite implement
|
|||||||
@Override
|
@Override
|
||||||
public void createTypeConstructor() {
|
public void createTypeConstructor() {
|
||||||
super.createTypeConstructor();
|
super.createTypeConstructor();
|
||||||
|
for (FunctionDescriptor functionDescriptor : getConstructors()) {
|
||||||
|
((ConstructorDescriptorImpl) functionDescriptor).setReturnType(getDefaultType());
|
||||||
|
}
|
||||||
scopeForMemberResolution.setImplicitReceiver(new ClassReceiver(this));
|
scopeForMemberResolution.setImplicitReceiver(new ClassReceiver(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-19
@@ -36,9 +36,8 @@ import java.util.*;
|
|||||||
/**
|
/**
|
||||||
* @author Stepan Koltsov
|
* @author Stepan Koltsov
|
||||||
*/
|
*/
|
||||||
public class MutableClassDescriptorLite extends ClassDescriptorBase
|
public abstract class MutableClassDescriptorLite extends ClassDescriptorBase
|
||||||
implements WithDeferredResolve {
|
implements WithDeferredResolve {
|
||||||
private final Set<ConstructorDescriptor> constructors = Sets.newLinkedHashSet();
|
|
||||||
|
|
||||||
private List<AnnotationDescriptor> annotations = Lists.newArrayList();
|
private List<AnnotationDescriptor> annotations = Lists.newArrayList();
|
||||||
|
|
||||||
@@ -134,9 +133,6 @@ public class MutableClassDescriptorLite extends ClassDescriptorBase
|
|||||||
getName().getName(),
|
getName().getName(),
|
||||||
typeParameters,
|
typeParameters,
|
||||||
supertypes);
|
supertypes);
|
||||||
for (FunctionDescriptor functionDescriptor : constructors) {
|
|
||||||
((ConstructorDescriptorImpl) functionDescriptor).setReturnType(getDefaultType());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private WritableScope getScopeForMemberLookupAsWritableScope() {
|
private WritableScope getScopeForMemberLookupAsWritableScope() {
|
||||||
@@ -145,12 +141,6 @@ public class MutableClassDescriptorLite extends ClassDescriptorBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
@Override
|
|
||||||
public Collection<ConstructorDescriptor> getConstructors() {
|
|
||||||
return constructors;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public JetScope getScopeForMemberLookup() {
|
public JetScope getScopeForMemberLookup() {
|
||||||
return scopeForMemberLookup;
|
return scopeForMemberLookup;
|
||||||
@@ -205,14 +195,6 @@ public class MutableClassDescriptorLite extends ClassDescriptorBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void addConstructor(@NotNull ConstructorDescriptor constructorDescriptor, @Nullable BindingTrace trace) {
|
|
||||||
assert constructorDescriptor.getContainingDeclaration() == this;
|
|
||||||
constructors.add(constructorDescriptor);
|
|
||||||
if (defaultType != null) {
|
|
||||||
((ConstructorDescriptorImpl) constructorDescriptor).setReturnType(getDefaultType());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
public MutableClassDescriptorLite getClassObjectDescriptor() {
|
public MutableClassDescriptorLite getClassObjectDescriptor() {
|
||||||
|
|||||||
Reference in New Issue
Block a user