mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
Improve reuse of nodes in signatures with type mapping (#58546)
This commit is contained in:
+90
-35
@@ -740,6 +740,7 @@ import {
|
||||
isThisInitializedObjectBindingExpression,
|
||||
isThisInTypeQuery,
|
||||
isThisProperty,
|
||||
isThisTypeNode,
|
||||
isThisTypeParameter,
|
||||
isThisTypePredicate,
|
||||
isTransientSymbol,
|
||||
@@ -5970,7 +5971,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
function isClassInstanceSide(type: Type) {
|
||||
return !!type.symbol && !!(type.symbol.flags & SymbolFlags.Class) && (type === getDeclaredTypeOfClassOrInterface(type.symbol) || (!!(type.flags & TypeFlags.Object) && !!(getObjectFlags(type) & ObjectFlags.IsClassInstanceClone)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as getTypeFromTypeNode, but for use in createNodeBuilder
|
||||
* Inside createNodeBuilder we shadow getTypeFromTypeNode to make sure anyone using this function will call the local version that does type mapping if appropriate
|
||||
* This function is used to still be able to call the original getTypeFromTypeNode from the local scope version of getTypeFromTypeNode
|
||||
*/
|
||||
function getTypeFromTypeNodeWithoutContext(node: TypeNode) {
|
||||
return getTypeFromTypeNode(node);
|
||||
}
|
||||
function createNodeBuilder() {
|
||||
return {
|
||||
typeToTypeNode: (type: Type, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => withContext(enclosingDeclaration, flags, tracker, context => typeToTypeNodeHelper(type, context)),
|
||||
@@ -5989,6 +5997,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
symbolToNode: (symbol: Symbol, meaning: SymbolFlags, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => withContext(enclosingDeclaration, flags, tracker, context => symbolToNode(symbol, context, meaning)),
|
||||
};
|
||||
|
||||
function getTypeFromTypeNode(context: NodeBuilderContext, node: TypeNode, noMappedTypes?: false): Type;
|
||||
function getTypeFromTypeNode(context: NodeBuilderContext, node: TypeNode, noMappedTypes: true): Type | undefined;
|
||||
function getTypeFromTypeNode(context: NodeBuilderContext, node: TypeNode, noMappedTypes?: boolean): Type | undefined {
|
||||
const type = getTypeFromTypeNodeWithoutContext(node);
|
||||
if (!context.mapper) return type;
|
||||
|
||||
const mappedType = instantiateType(type, context.mapper);
|
||||
return noMappedTypes && mappedType !== type ? undefined : mappedType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlike the utilities `setTextRange`, this checks if the `location` we're trying to set on `range` is within the
|
||||
* same file as the active context. If not, the range is not applied. This prevents us from copying ranges across files,
|
||||
@@ -6058,7 +6076,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
}
|
||||
const clone = tryReuseExistingNonParameterTypeNode(context, typeNode, type, host);
|
||||
if (clone) {
|
||||
if (addUndefined && !someType(getTypeFromTypeNode(typeNode), t => !!(t.flags & TypeFlags.Undefined))) {
|
||||
if (addUndefined && !someType(getTypeFromTypeNode(context, typeNode), t => !!(t.flags & TypeFlags.Undefined))) {
|
||||
return factory.createUnionTypeNode([clone, factory.createKeywordTypeNode(SyntaxKind.UndefinedKeyword)]);
|
||||
}
|
||||
return clone;
|
||||
@@ -6077,9 +6095,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
existing: TypeNode,
|
||||
type: Type,
|
||||
host = context.enclosingDeclaration,
|
||||
annotationType?: Type,
|
||||
annotationType = getTypeFromTypeNode(context, existing, /*noMappedTypes*/ true),
|
||||
) {
|
||||
if (typeNodeIsEquivalentToType(existing, host, type, annotationType) && existingTypeNodeIsNotReferenceOrIsReferenceWithCompatibleTypeArgumentCount(existing, type)) {
|
||||
if (annotationType && typeNodeIsEquivalentToType(host, type, annotationType) && existingTypeNodeIsNotReferenceOrIsReferenceWithCompatibleTypeArgumentCount(existing, type)) {
|
||||
const result = tryReuseExistingTypeNodeHelper(context, existing);
|
||||
if (result) {
|
||||
return result;
|
||||
@@ -6132,6 +6150,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
typeParameterNames: undefined,
|
||||
typeParameterNamesByText: undefined,
|
||||
typeParameterNamesByTextNextNameCount: undefined,
|
||||
mapper: undefined,
|
||||
};
|
||||
context.tracker = new SymbolTrackerImpl(context, tracker, moduleResolverHost);
|
||||
const resultingNode = cb(context);
|
||||
@@ -6421,8 +6440,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
context.inferTypeParameters = type.root.inferTypeParameters;
|
||||
const extendsTypeNode = typeToTypeNodeHelper(instantiateType(type.root.extendsType, newMapper), context);
|
||||
context.inferTypeParameters = saveInferTypeParameters;
|
||||
const trueTypeNode = typeToTypeNodeOrCircularityElision(instantiateType(getTypeFromTypeNode(type.root.node.trueType), newMapper));
|
||||
const falseTypeNode = typeToTypeNodeOrCircularityElision(instantiateType(getTypeFromTypeNode(type.root.node.falseType), newMapper));
|
||||
const trueTypeNode = typeToTypeNodeOrCircularityElision(instantiateType(getTypeFromTypeNode(context, type.root.node.trueType), newMapper));
|
||||
const falseTypeNode = typeToTypeNodeOrCircularityElision(instantiateType(getTypeFromTypeNode(context, type.root.node.falseType), newMapper));
|
||||
|
||||
// outermost conditional makes `T` a type parameter, allowing the inner conditionals to be distributive
|
||||
// second conditional makes `T` have `T & checkType` substitution, so it is correctly usable as the checkType
|
||||
@@ -6519,7 +6538,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
// homomorphic mapped type with a non-homomorphic naive inlining
|
||||
// wrap it with a conditional like `SomeModifiersType extends infer U ? {..the mapped type...} : never` to ensure the resulting
|
||||
// type stays homomorphic
|
||||
const originalConstraint = instantiateType(getConstraintOfTypeParameter(getTypeFromTypeNode((type.declaration.typeParameter.constraint! as TypeOperatorNode).type) as TypeParameter) || unknownType, type.mapper);
|
||||
const originalConstraint = instantiateType(getConstraintOfTypeParameter(getTypeFromTypeNode(context, (type.declaration.typeParameter.constraint! as TypeOperatorNode).type) as TypeParameter) || unknownType, type.mapper);
|
||||
return factory.createConditionalTypeNode(
|
||||
typeToTypeNodeHelper(getModifiersTypeFromMappedType(type), context),
|
||||
factory.createInferTypeNode(factory.createTypeParameterDeclaration(/*modifiers*/ undefined, factory.cloneNode(newTypeVariable!.typeName) as Identifier, originalConstraint.flags & TypeFlags.Unknown ? undefined : typeToTypeNodeHelper(originalConstraint, context))),
|
||||
@@ -7205,7 +7224,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
let typeArguments: TypeNode[] | undefined;
|
||||
|
||||
const expandedParams = getExpandedParameters(signature, /*skipUnionExpanding*/ true)[0];
|
||||
const cleanup = enterNewScope(context, signature.declaration, expandedParams, signature.typeParameters, signature.parameters);
|
||||
const cleanup = enterNewScope(context, signature.declaration, expandedParams, signature.typeParameters, signature.parameters, signature.mapper);
|
||||
context.approximateLength += 3; // Usually a signature contributes a few more characters than this, but 3 is the minimum
|
||||
|
||||
if (context.flags & NodeBuilderFlags.WriteTypeArgumentsOfSignature && signature.target && signature.mapper && signature.target.typeParameters) {
|
||||
@@ -7285,6 +7304,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
expandedParams: readonly Symbol[] | undefined,
|
||||
typeParameters: readonly TypeParameter[] | undefined,
|
||||
originalParameters?: readonly Symbol[] | undefined,
|
||||
mapper?: TypeMapper,
|
||||
) {
|
||||
const cleanupContext = cloneNodeBuilderContext(context);
|
||||
// For regular function/method declarations, the enclosing declaration will already be signature.declaration,
|
||||
@@ -7302,6 +7322,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
let cleanupParams: (() => void) | undefined;
|
||||
let cleanupTypeParams: (() => void) | undefined;
|
||||
const oldEnclosingDecl = context.enclosingDeclaration;
|
||||
const oldMapper = context.mapper;
|
||||
if (mapper) {
|
||||
context.mapper = mapper;
|
||||
}
|
||||
if (context.enclosingDeclaration && declaration) {
|
||||
// As a performance optimization, reuse the same fake scope within this chain.
|
||||
// This is especially needed when we are working on an excessively deep type;
|
||||
@@ -7434,6 +7458,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
cleanupTypeParams?.();
|
||||
cleanupContext();
|
||||
context.enclosingDeclaration = oldEnclosingDecl;
|
||||
context.mapper = oldMapper;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -7449,7 +7474,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
/*dotDotDotToken*/ undefined,
|
||||
"this",
|
||||
/*questionToken*/ undefined,
|
||||
typeToTypeNodeHelper(getTypeFromTypeNode(thisTag.typeExpression), context),
|
||||
typeToTypeNodeHelper(getTypeFromTypeNode(context, thisTag.typeExpression), context),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -8144,7 +8169,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
};
|
||||
}
|
||||
|
||||
function getDeclarationWithTypeAnnotation(symbol: Symbol, enclosingDeclaration: Node | undefined) {
|
||||
function getDeclarationWithTypeAnnotation(symbol: Symbol, enclosingDeclaration?: Node | undefined) {
|
||||
return symbol.declarations && find(symbol.declarations, s => !!getNonlocalEffectiveTypeAnnotationNode(s) && (!enclosingDeclaration || !!findAncestor(s, n => n === enclosingDeclaration)));
|
||||
}
|
||||
|
||||
@@ -8182,11 +8207,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
if (enclosingDeclaration && (!isErrorType(type) || (context.flags & NodeBuilderFlags.AllowUnresolvedNames))) {
|
||||
const declWithExistingAnnotation = declaration && getNonlocalEffectiveTypeAnnotationNode(declaration)
|
||||
? declaration
|
||||
: getDeclarationWithTypeAnnotation(symbol, getEnclosingDeclarationIgnoringFakeScope(enclosingDeclaration));
|
||||
: getDeclarationWithTypeAnnotation(symbol);
|
||||
if (declWithExistingAnnotation && !isFunctionLikeDeclaration(declWithExistingAnnotation) && !isGetAccessorDeclaration(declWithExistingAnnotation)) {
|
||||
// try to reuse the existing annotation
|
||||
const existing = getNonlocalEffectiveTypeAnnotationNode(declWithExistingAnnotation)!;
|
||||
const result = tryReuseExistingTypeNode(context, existing, type, declWithExistingAnnotation, addUndefined);
|
||||
const result = !isTypePredicateNode(existing) && tryReuseExistingTypeNode(context, existing, type, declWithExistingAnnotation, addUndefined);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
@@ -8212,7 +8237,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
return result;
|
||||
}
|
||||
|
||||
function typeNodeIsEquivalentToType(typeNode: TypeNode, annotatedDeclaration: Node | undefined, type: Type, typeFromTypeNode = getTypeFromTypeNode(typeNode)) {
|
||||
function typeNodeIsEquivalentToType(annotatedDeclaration: Node | undefined, type: Type, typeFromTypeNode: Type) {
|
||||
if (typeFromTypeNode === type) {
|
||||
return true;
|
||||
}
|
||||
@@ -8245,13 +8270,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
function serializeReturnTypeForSignatureWorker(context: NodeBuilderContext, signature: Signature) {
|
||||
const typePredicate = getTypePredicateOfSignature(signature);
|
||||
const type = getReturnTypeOfSignature(signature);
|
||||
if (context.enclosingDeclaration && (!isErrorType(type) || (context.flags & NodeBuilderFlags.AllowUnresolvedNames))) {
|
||||
if (context.enclosingDeclaration && (!isErrorType(type) || (context.flags & NodeBuilderFlags.AllowUnresolvedNames)) && signature.declaration && !nodeIsSynthesized(signature.declaration)) {
|
||||
const annotation = signature.declaration && getNonlocalEffectiveReturnTypeAnnotationNode(signature.declaration);
|
||||
const enclosingDeclarationIgnoringFakeScope = getEnclosingDeclarationIgnoringFakeScope(context.enclosingDeclaration);
|
||||
if (!!findAncestor(annotation, n => n === enclosingDeclarationIgnoringFakeScope) && annotation) {
|
||||
const annotated = getTypeFromTypeNode(annotation);
|
||||
const thisInstantiated = annotated.flags & TypeFlags.TypeParameter && (annotated as TypeParameter).isThisType ? instantiateType(annotated, signature.mapper) : annotated;
|
||||
const result = tryReuseExistingNonParameterTypeNode(context, annotation, type, signature.declaration, thisInstantiated);
|
||||
// Default constructor signatures inherited from base classes return the derived class but have the base class declaration
|
||||
// To ensure we don't serialize the wrong type we check that that return type of the signature corresponds to the declaration return type signature
|
||||
if (annotation && getTypeFromTypeNode(context, annotation) === type) {
|
||||
const result = tryReuseExistingTypeNodeHelper(context, annotation);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
@@ -8382,13 +8406,21 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
);
|
||||
}
|
||||
}
|
||||
if (isThisTypeNode(existing)) {
|
||||
if (context.mapper === undefined) return true;
|
||||
const type = getTypeFromTypeNode(context, existing, /*noMappedTypes*/ true);
|
||||
return !!type;
|
||||
}
|
||||
if (isTypeReferenceNode(existing)) {
|
||||
if (isConstTypeReference(existing)) return false;
|
||||
const type = getTypeFromTypeReference(existing);
|
||||
const symbol = getNodeLinks(existing).resolvedSymbol;
|
||||
if (!symbol) return false;
|
||||
if (symbol.flags & SymbolFlags.TypeParameter) {
|
||||
return true;
|
||||
const type = getDeclaredTypeOfSymbol(symbol);
|
||||
if (context.mapper && getMappedType(type, context.mapper) !== type) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (isInJSDoc(existing)) {
|
||||
return existingTypeNodeIsNotReferenceOrIsReferenceWithCompatibleTypeArgumentCount(existing, type)
|
||||
@@ -8408,7 +8440,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
}
|
||||
|
||||
function serializeExistingTypeNode(context: NodeBuilderContext, typeNode: TypeNode) {
|
||||
const type = getTypeFromTypeNode(typeNode);
|
||||
const type = getTypeFromTypeNode(context, typeNode);
|
||||
return typeToTypeNodeHelper(type, context);
|
||||
}
|
||||
|
||||
@@ -8472,8 +8504,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
if (isJSDocTypeLiteral(node)) {
|
||||
return factory.createTypeLiteralNode(map(node.jsDocPropertyTags, t => {
|
||||
const name = isIdentifier(t.name) ? t.name : t.name.right;
|
||||
const typeViaParent = getTypeOfPropertyOfType(getTypeFromTypeNode(node), name.escapedText);
|
||||
const overrideTypeNode = typeViaParent && t.typeExpression && getTypeFromTypeNode(t.typeExpression.type) !== typeViaParent ? typeToTypeNodeHelper(typeViaParent, context) : undefined;
|
||||
const typeViaParent = getTypeOfPropertyOfType(getTypeFromTypeNode(context, node), name.escapedText);
|
||||
const overrideTypeNode = typeViaParent && t.typeExpression && getTypeFromTypeNode(context, t.typeExpression.type) !== typeViaParent ? typeToTypeNodeHelper(typeViaParent, context) : undefined;
|
||||
|
||||
return factory.createPropertySignature(
|
||||
/*modifiers*/ undefined,
|
||||
@@ -8509,7 +8541,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
p.name && isIdentifier(p.name) && p.name.escapedText === "new" ? (newTypeNode = p.type, undefined) : factory.createParameterDeclaration(
|
||||
/*modifiers*/ undefined,
|
||||
getEffectiveDotDotDotForParameter(p),
|
||||
getNameForJSDocFunctionParameter(p, i),
|
||||
setTextRange(context, factory.createIdentifier(getNameForJSDocFunctionParameter(p, i)), p),
|
||||
p.questionToken,
|
||||
visitNode(p.type, visitExistingNodeTreeSymbols, isTypeNode),
|
||||
/*initializer*/ undefined,
|
||||
@@ -8524,7 +8556,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
factory.createParameterDeclaration(
|
||||
/*modifiers*/ undefined,
|
||||
getEffectiveDotDotDotForParameter(p),
|
||||
getNameForJSDocFunctionParameter(p, i),
|
||||
setTextRange(context, factory.createIdentifier(getNameForJSDocFunctionParameter(p, i)), p),
|
||||
p.questionToken,
|
||||
visitNode(p.type, visitExistingNodeTreeSymbols, isTypeNode),
|
||||
/*initializer*/ undefined,
|
||||
@@ -8533,6 +8565,21 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
);
|
||||
}
|
||||
}
|
||||
if (isThisTypeNode(node)) {
|
||||
if (canReuseTypeNode(context, node)) {
|
||||
return node;
|
||||
}
|
||||
return serializeExistingTypeNode(context, node);
|
||||
}
|
||||
if (isTypeParameterDeclaration(node)) {
|
||||
return factory.updateTypeParameterDeclaration(
|
||||
node,
|
||||
node.modifiers,
|
||||
setTextRange(context, typeParameterToName(getDeclaredTypeOfSymbol(getSymbolOfDeclaration(node)), context), node),
|
||||
visitNode(node.constraint, visitExistingNodeTreeSymbols, isTypeNode),
|
||||
visitNode(node.default, visitExistingNodeTreeSymbols, isTypeNode),
|
||||
);
|
||||
}
|
||||
if (isTypeReferenceNode(node)) {
|
||||
if (canReuseTypeNode(context, node)) {
|
||||
const { introducesError, node: newName } = trackExistingEntityName(node.typeName, context);
|
||||
@@ -8567,7 +8614,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
!(length(node.typeArguments) >= getMinTypeArgumentCount(getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(nodeSymbol)))
|
||||
)
|
||||
) {
|
||||
return setTextRange(context, typeToTypeNodeHelper(getTypeFromTypeNode(node), context), node);
|
||||
return setTextRange(context, typeToTypeNodeHelper(getTypeFromTypeNode(context, node), context), node);
|
||||
}
|
||||
return factory.updateImportTypeNode(
|
||||
node,
|
||||
@@ -8633,13 +8680,20 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
return factory.updateComputedPropertyName(node, literal);
|
||||
}
|
||||
}
|
||||
if (isTypePredicateNode(node) && isIdentifier(node.parameterName)) {
|
||||
const { node: result, introducesError } = trackExistingEntityName(node.parameterName, context);
|
||||
// Should not usually happen the only case is when a type predicate comes from a JSDoc type annotation with it's own parameter symbol definition.
|
||||
// /** @type {(v: unknown) => v is undefined} */
|
||||
// const isUndef = v => v === undefined;
|
||||
hadError = hadError || introducesError;
|
||||
return factory.updateTypePredicateNode(node, node.assertsModifier, result, visitNode(node.type, visitExistingNodeTreeSymbols, isTypeNode));
|
||||
if (isTypePredicateNode(node)) {
|
||||
let parameterName;
|
||||
if (isIdentifier(node.parameterName)) {
|
||||
const { node: result, introducesError } = trackExistingEntityName(node.parameterName, context);
|
||||
// Should not usually happen the only case is when a type predicate comes from a JSDoc type annotation with it's own parameter symbol definition.
|
||||
// /** @type {(v: unknown) => v is undefined} */
|
||||
// const isUndef = v => v === undefined;
|
||||
hadError = hadError || introducesError;
|
||||
parameterName = result;
|
||||
}
|
||||
else {
|
||||
parameterName = node.parameterName;
|
||||
}
|
||||
return factory.updateTypePredicateNode(node, node.assertsModifier, parameterName, visitNode(node.type, visitExistingNodeTreeSymbols, isTypeNode));
|
||||
}
|
||||
|
||||
if (isTupleTypeNode(node) || isTypeLiteralNode(node) || isMappedTypeNode(node)) {
|
||||
@@ -9478,8 +9532,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
return cleanup(factory.createExpressionWithTypeArguments(
|
||||
expr,
|
||||
map(e.typeArguments, a =>
|
||||
tryReuseExistingNonParameterTypeNode(context, a, getTypeFromTypeNode(a))
|
||||
|| typeToTypeNodeHelper(getTypeFromTypeNode(a), context)),
|
||||
tryReuseExistingNonParameterTypeNode(context, a, getTypeFromTypeNode(context, a))
|
||||
|| typeToTypeNodeHelper(getTypeFromTypeNode(context, a), context)),
|
||||
));
|
||||
|
||||
function cleanup<T>(result: T): T {
|
||||
@@ -51616,6 +51670,7 @@ interface NodeBuilderContext {
|
||||
remappedSymbolReferences: Map<SymbolId, Symbol> | undefined;
|
||||
reverseMappedStack: ReverseMappedSymbol[] | undefined;
|
||||
bundled: boolean;
|
||||
mapper: TypeMapper | undefined;
|
||||
}
|
||||
|
||||
class SymbolTrackerImpl implements SymbolTracker {
|
||||
|
||||
@@ -557,7 +557,7 @@ function getSymbolDisplayPartsDocumentationAndSymbolKindWorker(typeChecker: Type
|
||||
typeChecker,
|
||||
resolvedSymbol,
|
||||
getSourceFileOfNode(resolvedNode),
|
||||
resolvedNode,
|
||||
enclosingDeclaration,
|
||||
declarationName,
|
||||
type,
|
||||
semanticMeaning,
|
||||
|
||||
Reference in New Issue
Block a user