use of com.jetbrains.php.lang.psi.resolve.types.PhpType in project phpinspectionsea by kalessil.
the class SenselessProxyMethodInspector method buildVisitor.
@Override
@NotNull
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
return new BasePhpElementVisitor() {
@Override
public void visitPhpClass(@NotNull PhpClass clazz) {
if (clazz.isInterface() || clazz.isTrait()) {
return;
}
for (final Method method : clazz.getOwnMethods()) {
final PsiElement methodNameNode = NamedElementUtil.getNameIdentifier(method);
if (null == methodNameNode || method.isAbstract() || method.getAccess().isPrivate()) {
continue;
}
/* we expect the method to have just one expression - parent invocation */
final GroupStatement body = ExpressionSemanticUtil.getGroupStatement(method);
if (null == body || 1 != ExpressionSemanticUtil.countExpressionsInGroup(body)) {
continue;
}
final PsiElement lastStatement = ExpressionSemanticUtil.getLastStatement(body);
if (null == lastStatement) {
continue;
}
/* parent invocation can be both direct or via return */
final PsiElement parentReferenceCandidate;
if (lastStatement instanceof PhpReturn) {
parentReferenceCandidate = ExpressionSemanticUtil.getReturnValue((PhpReturn) lastStatement);
} else {
parentReferenceCandidate = lastStatement.getFirstChild();
}
if (!(parentReferenceCandidate instanceof MethodReference)) {
continue;
}
final MethodReference reference = (MethodReference) parentReferenceCandidate;
final String referenceVariable = reference.getFirstChild().getText().trim();
final String referenceName = reference.getName();
if (null == referenceName || !referenceVariable.equals("parent") || !referenceName.equals(method.getName())) {
continue;
}
final Parameter[] methodParameters = method.getParameters();
/* ensure no transformations/reordering happens when dispatching parameters */
final PsiElement[] givenParams = reference.getParameters();
boolean isDispatchingWithoutModifications = (givenParams.length == methodParameters.length);
if (isDispatchingWithoutModifications) {
/* ensure parameters re-dispatched in the same order and state */
for (int index = 0; index < givenParams.length; ++index) {
if (!(givenParams[index] instanceof Variable) || !((Variable) givenParams[index]).getName().equals(methodParameters[index].getName())) {
isDispatchingWithoutModifications = false;
break;
}
}
}
/* ensure no signature changes took place */
boolean isChangingSignature = false;
final PsiReference referenceToMethod = reference.getReference();
if (null != referenceToMethod && isDispatchingWithoutModifications) {
final PsiElement referenceResolved = OpenapiResolveUtil.resolveReference(referenceToMethod);
if (referenceResolved instanceof Method) {
final Method nestedMethod = (Method) referenceResolved;
final Parameter[] parentParameters = nestedMethod.getParameters();
/* verify amount of parameters, visibility, static, abstract, final */
if (parentParameters.length == methodParameters.length && nestedMethod.isAbstract() == method.isAbstract() && nestedMethod.isStatic() == method.isStatic() && nestedMethod.isFinal() == method.isFinal() && nestedMethod.getAccess().equals(method.getAccess())) {
/* analyze if parameters definition has been changed (only ignore naming changes) */
if (methodParameters.length > 0) {
for (int index = 0; index < parentParameters.length; ++index) {
/* by-reference declaration changes: not allowed by PHP, hence not checked */
/* default values changes */
final PsiElement parentDefault = parentParameters[index].getDefaultValue();
final PsiElement methodDefault = methodParameters[index].getDefaultValue();
if ((parentDefault == null || methodDefault == null) && parentDefault != methodDefault) {
isChangingSignature = true;
break;
}
if (methodDefault != null && !OpenapiEquivalenceUtil.areEqual(parentDefault, methodDefault)) {
isChangingSignature = true;
break;
}
/* false-positive: magic constants ARE changing signature */
if (methodDefault instanceof ConstantReference) {
final String constant = ((ConstantReference) methodDefault).getName();
if (constants.contains(constant)) {
isChangingSignature = true;
break;
}
}
/* type definition changes */
final PhpType parentType = OpenapiResolveUtil.resolveDeclaredType(parentParameters[index]);
final PhpType methodType = OpenapiResolveUtil.resolveDeclaredType(methodParameters[index]);
if (!parentType.equals(methodType)) {
isChangingSignature = true;
break;
}
}
}
/* verify returned type declaration */
if (!isChangingSignature) {
final PsiElement methodReturn = OpenapiElementsUtil.getReturnType(method);
final PsiElement parentReturn = OpenapiElementsUtil.getReturnType(nestedMethod);
if (methodReturn != parentReturn) {
isChangingSignature = methodReturn == null || parentReturn == null || !OpenapiEquivalenceUtil.areEqual(methodReturn, parentReturn);
}
}
} else {
/* okay obviously changed */
isChangingSignature = true;
}
} else {
/* we couldn't resolve parent, so we can't report anything */
isChangingSignature = true;
}
}
/* decide if need to report any issues */
if (isDispatchingWithoutModifications && !isChangingSignature) {
holder.registerProblem(methodNameNode, MessagesPresentationUtil.prefixWithEa(messagePattern.replace("%s%", method.getName())), ProblemHighlightType.WEAK_WARNING, new DropMethodFix());
}
}
}
};
}
use of com.jetbrains.php.lang.psi.resolve.types.PhpType in project phpinspectionsea by kalessil.
the class NullableVariablesStrategy method isNullableResult.
private static boolean isNullableResult(@NotNull AssignmentExpression assignment, @NotNull Project project) {
boolean result = false;
final PsiElement assignmentValue = assignment.getValue();
/* primary strategy: resolve types and check nullability */
if (assignmentValue instanceof PhpTypedElement) {
final PhpType resolved = OpenapiResolveUtil.resolveType((PhpTypedElement) assignmentValue, project);
if (resolved != null) {
final Set<String> types = new HashSet<>();
resolved.filterUnknown().getTypes().forEach(t -> types.add(Types.getType(t)));
if (types.contains(Types.strNull) || types.contains(Types.strVoid)) {
types.remove(Types.strNull);
types.remove(Types.strVoid);
if (!types.isEmpty()) {
result = types.stream().noneMatch(t -> !t.startsWith("\\") && !objectTypes.contains(t));
}
}
types.clear();
}
}
/* secondary strategy: support type specification with `@var <type> <variable>` */
if (result) {
final PhpPsiElement variable = assignment.getVariable();
final PsiElement parent = assignment.getParent();
if (variable != null && OpenapiTypesUtil.isStatementImpl(parent) && OpenapiTypesUtil.isAssignment(assignment)) {
final PsiElement previous = ((PhpPsiElement) parent).getPrevPsiSibling();
if (previous instanceof PhpDocComment) {
final PhpDocTag[] hints = ((PhpDocComment) previous).getTagElementsByName("@var");
if (hints.length == 1) {
final PhpDocVariable specifiedVariable = PsiTreeUtil.findChildOfType(hints[0], PhpDocVariable.class);
if (specifiedVariable != null && specifiedVariable.getName().equals(variable.getName())) {
result = Arrays.stream(hints[0].getChildren()).anyMatch(t -> t instanceof PhpDocType && Types.getType(t.getText()).equals(Types.strNull));
}
}
}
}
}
return result;
}
use of com.jetbrains.php.lang.psi.resolve.types.PhpType in project phpinspectionsea by kalessil.
the class StrlenInEmptyStringCheckContextInspection method buildVisitor.
@Override
@NotNull
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
return new BasePhpElementVisitor() {
@Override
public void visitPhpFunctionCall(@NotNull FunctionReference reference) {
final String functionName = reference.getName();
if (functionName != null && (functionName.equals("strlen") || functionName.equals("mb_strlen"))) {
final PsiElement[] arguments = reference.getParameters();
if (arguments.length > 0 && ExpressionSemanticUtil.getBlockScope(reference) != null) {
boolean isMatchedPattern = false;
boolean isEmptyString = false;
PsiElement target = null;
/* check explicit numbers comparisons */
final PsiElement parent = reference.getParent();
if (parent instanceof BinaryExpression) {
final BinaryExpression binary = (BinaryExpression) parent;
final PsiElement left = binary.getLeftOperand();
final PsiElement secondOperand = OpenapiElementsUtil.getSecondOperand(binary, reference);
/* second operand should be a number */
if (OpenapiTypesUtil.isNumber(secondOperand)) {
final String number = secondOperand.getText();
/* check cases when comparing with 1 */
final IElementType operator = binary.getOperationType();
if (operator == PhpTokenTypes.opGREATER) {
isMatchedPattern = left == reference && number.equals("0");
target = binary;
isEmptyString = false;
} else if (operator == PhpTokenTypes.opLESS || operator == PhpTokenTypes.opGREATER_OR_EQUAL) {
isMatchedPattern = left == reference && number.equals("1");
target = binary;
isEmptyString = operator == PhpTokenTypes.opLESS;
}
/* check cases when checking equality to 0 */
if (!isMatchedPattern && OpenapiTypesUtil.tsCOMPARE_EQUALITY_OPS.contains(operator)) {
isMatchedPattern = number.equals("0");
target = binary;
isEmptyString = operator == PhpTokenTypes.opIDENTICAL || operator == PhpTokenTypes.opEQUAL;
}
}
}
/* checks NON-implicit boolean comparison patterns */
if (!isMatchedPattern && ExpressionSemanticUtil.isUsedAsLogicalOperand(reference)) {
isMatchedPattern = true;
target = reference;
final PsiElement operation = parent instanceof UnaryExpression ? ((UnaryExpression) parent).getOperation() : null;
if (operation != null) {
isEmptyString = OpenapiTypesUtil.is(operation, PhpTokenTypes.opNOT);
target = parent;
}
}
/* investigate possible issues */
if (isMatchedPattern) {
final boolean isRegular = ComparisonStyle.isRegular();
final String operator = (isEmptyString ? "=" : "!") + (this.canApplyIdentityOperator(arguments[0]) ? "==" : "=");
final String replacement = String.format(isRegular ? "%s %s ''" : "'' %s %s", isRegular ? arguments[0].getText() : operator, isRegular ? operator : arguments[0].getText());
holder.registerProblem(target, String.format(MessagesPresentationUtil.prefixWithEa(messagePattern), replacement), new CompareToEmptyStringFix(replacement));
}
}
}
}
private boolean canApplyIdentityOperator(@NotNull PsiElement value) {
if (value instanceof PhpTypedElement) {
final PhpType resolved = OpenapiResolveUtil.resolveType((PhpTypedElement) value, holder.getProject());
if (resolved != null && resolved.size() == 1) {
return Types.strString.equals(Types.getType(resolved.getTypes().iterator().next()));
}
}
return false;
}
};
}
use of com.jetbrains.php.lang.psi.resolve.types.PhpType in project phpinspectionsea by kalessil.
the class OpenapiResolveUtil method resolveType.
@Nullable
public static PhpType resolveType(@NotNull PhpTypedElement expression, @NotNull Project project) {
PhpType result = null;
try {
if (expression instanceof StringLiteralExpression) {
result = new PhpType().add(PhpType.STRING);
} else if (expression instanceof ConstantReference) {
final ConstantReference reference = (ConstantReference) expression;
final String referenceName = reference.getName();
if (referenceName != null && !referenceName.isEmpty()) {
final PsiElement resolved = OpenapiResolveUtil.resolveReference(reference);
if (resolved instanceof PhpDefine) {
final PsiElement value = ((PhpDefine) resolved).getValue();
if (value instanceof PhpTypedElement) {
result = resolveType((PhpTypedElement) value, project);
}
}
}
} else if (expression instanceof ClassConstantReference) {
final ClassConstantReference reference = (ClassConstantReference) expression;
final String referenceName = reference.getName();
if (referenceName != null && !referenceName.isEmpty()) {
final PsiElement resolved = resolveReference(reference);
if (resolved instanceof Field && ((Field) resolved).isConstant()) {
final PsiElement value = ((Field) resolved).getDefaultValue();
if (value instanceof PhpTypedElement) {
result = resolveType((PhpTypedElement) value, project);
}
}
}
} else if (expression instanceof FunctionReference) {
/* resolve function and get it's type or fallback to empty type */
final FunctionReference reference = (FunctionReference) expression;
final PsiElement function = resolveReference(reference);
result = function instanceof Function ? ((Function) function).getType().global(project) : new PhpType();
if (!(function instanceof Method)) {
final String name = reference.getName();
/* override signatures if we specified custom signatures */
if (name != null && functionReturnTypes.containsKey(name)) {
result = functionReturnTypes.get(name);
}
/* some replacement functions result can be narrowed from arguments type */
if (name != null && functionToNarrow.containsKey(name)) {
final int targetPosition = functionToNarrow.get(name);
final PsiElement[] arguments = reference.getParameters();
if (arguments.length > targetPosition && arguments[targetPosition] instanceof PhpTypedElement) {
final PhpType argumentType = resolveType((PhpTypedElement) arguments[targetPosition], project);
if (argumentType != null && !argumentType.isEmpty() && !argumentType.hasUnknown()) {
if (argumentType.getTypes().stream().noneMatch(t -> Types.getType(t).equals(Types.strArray))) {
result.getTypes().removeIf(t -> Types.getType(t).equals(Types.strArray));
}
if (argumentType.getTypes().stream().noneMatch(t -> Types.getType(t).equals(Types.strString))) {
result.getTypes().removeIf(t -> Types.getType(t).equals(Types.strString));
}
}
}
} else if (name != null) {
if (name.equals("explode")) {
/* explode return false if delimiter is an empty string */
final PsiElement[] arguments = reference.getParameters();
if (arguments.length >= 2 && arguments[0] instanceof StringLiteralExpression) {
final String content = ((StringLiteralExpression) arguments[0]).getContents();
result = new PhpType().add(content.isEmpty() ? PhpType.BOOLEAN : PhpType.ARRAY);
}
} else if (name.equals("parse_url")) {
final PsiElement[] arguments = reference.getParameters();
if (arguments.length == 2 && arguments[1] instanceof ConstantReference) {
final String constantName = ((ConstantReference) arguments[1]).getName();
if (constantName != null && constantName.equals("PHP_URL_PORT")) {
result = new PhpType().add(PhpType.INT).add(PhpType.NULL);
} else {
result = new PhpType().add(PhpType.STRING).add(PhpType.NULL);
}
}
} else if (name.equals("microtime")) {
final PsiElement[] arguments = reference.getParameters();
if (arguments.length == 1 && !PhpLanguageUtil.isFalse(arguments[0])) {
result = new PhpType().add(PhpType.FLOAT);
} else {
result = new PhpType().add(PhpType.INT);
}
}
}
}
} else if (expression instanceof ArrayAccessExpression) {
/* `_GET[...] & co` gets resolved with missing string type */
final ArrayAccessExpression access = (ArrayAccessExpression) expression;
final PsiElement globalCandidate = access.getValue();
if (globalCandidate instanceof Variable) {
final String variableName = ((Variable) globalCandidate).getName();
if (ExpressionCostEstimateUtil.predefinedVars.contains(variableName)) {
final ArrayIndex holder = access.getIndex();
final PhpPsiElement index = holder == null ? null : holder.getValue();
if (variableName.equals("_SERVER") && index instanceof StringLiteralExpression) {
switch(((StringLiteralExpression) index).getContents()) {
case "argv":
result = new PhpType().add(PhpType.ARRAY);
break;
case "argc":
case "REQUEST_TIME":
case "REMOTE_PORT":
case "SERVER_PORT":
result = new PhpType().add(PhpType.INT);
break;
case "REQUEST_TIME_FLOAT":
result = new PhpType().add(PhpType.FLOAT);
break;
default:
result = new PhpType().add(PhpType.STRING);
break;
}
} else {
result = new PhpType().add(PhpType.STRING).add(PhpType.ARRAY);
}
}
}
} else if (expression instanceof BinaryExpression) {
final BinaryExpression binary = (BinaryExpression) expression;
final IElementType operator = binary.getOperationType();
if (operator == PhpTokenTypes.opPLUS || operator == PhpTokenTypes.opMINUS || operator == PhpTokenTypes.opMUL) {
/* workaround for https://youtrack.jetbrains.com/issue//WI-37466 & co */
boolean hasFloat = true;
boolean hasArray = false;
final PsiElement left = binary.getLeftOperand();
if (left instanceof PhpTypedElement) {
final PhpType leftType = resolveType((PhpTypedElement) left, project);
if (leftType != null) {
final Set<String> leftTypes = new HashSet<>();
leftType.filterUnknown().getTypes().forEach(type -> leftTypes.add(Types.getType(type)));
hasFloat = leftTypes.isEmpty() || leftTypes.contains(Types.strFloat) || leftTypes.contains(Types.strNumber) || (leftTypes.contains(Types.strString) && !leftTypes.contains(Types.strInteger));
hasArray = leftTypes.contains(Types.strArray);
leftTypes.clear();
if (!hasFloat || (!hasArray && operator == PhpTokenTypes.opPLUS)) {
final PsiElement right = binary.getRightOperand();
if (right instanceof PhpTypedElement) {
final PhpType rightType = resolveType((PhpTypedElement) right, project);
if (rightType != null) {
final Set<String> rightTypes = new HashSet<>();
rightType.filterUnknown().getTypes().forEach(type -> rightTypes.add(Types.getType(type)));
hasFloat = hasFloat || rightTypes.isEmpty() || rightTypes.contains(Types.strFloat) || rightTypes.contains(Types.strNumber) || (rightTypes.contains(Types.strString) && !rightTypes.contains(Types.strInteger));
hasArray = (hasArray && !OpenapiTypesUtil.isNumber(right)) || rightTypes.contains(Types.strArray);
rightTypes.clear();
}
}
}
}
}
result = hasFloat ? new PhpType().add(PhpType.FLOAT) : new PhpType().add(PhpType.INT);
result = hasArray ? new PhpType().add(PhpType.ARRAY) : result;
} else if (operator == PhpTokenTypes.opCOALESCE) {
/* workaround for https://youtrack.jetbrains.com/issue/WI-37013 & co */
final PsiElement left = binary.getLeftOperand();
final PsiElement right = binary.getRightOperand();
result = PhpType.EMPTY;
if (left instanceof PhpTypedElement && right instanceof PhpTypedElement) {
final PhpType leftType = resolveType((PhpTypedElement) left, project);
if (leftType != null && !leftType.filterUnknown().isEmpty()) {
final PhpType rightType = resolveType((PhpTypedElement) right, project);
if (rightType != null && !rightType.filterUnknown().isEmpty()) {
result = new PhpType().add(leftType.filterNull()).add(rightType);
}
}
}
}
} else if (expression instanceof TernaryExpression) {
final TernaryExpression ternary = (TernaryExpression) expression;
final PsiElement left = ternary.getTrueVariant();
final PsiElement right = ternary.getFalseVariant();
if (left instanceof PhpTypedElement && right instanceof PhpTypedElement) {
final PhpType leftType = resolveType((PhpTypedElement) left, project);
if (leftType != null && !leftType.filterUnknown().isEmpty()) {
final PhpType rightType = resolveType((PhpTypedElement) right, project);
if (rightType != null && !rightType.filterUnknown().isEmpty()) {
result = ternary.isShort() ? new PhpType().add(leftType.filterNull()).add(rightType) : new PhpType().add(leftType).add(rightType);
}
}
}
} else if (expression instanceof UnaryExpression) {
final UnaryExpression unary = (UnaryExpression) expression;
final PsiElement operation = unary.getOperation();
if (operation != null) {
if (OpenapiTypesUtil.is(operation, PhpTokenTypes.opBIT_NOT) || OpenapiTypesUtil.is(operation, PhpTokenTypes.opMINUS)) {
final PsiElement argument = unary.getValue();
if (argument instanceof PhpTypedElement) {
result = resolveType((PhpTypedElement) argument, project);
}
} else if (OpenapiTypesUtil.is(operation, PhpTokenTypes.kwCLONE)) {
final PsiElement argument = unary.getValue();
if (argument instanceof PhpTypedElement) {
final PhpType argumentType = resolveType((PhpTypedElement) argument, project);
if (argumentType != null && !argumentType.isEmpty()) {
result = new PhpType().add(argumentType.filterPrimitives());
}
}
}
}
} else if (expression instanceof AssignmentExpression && OpenapiTypesUtil.isAssignment((PsiElement) expression)) {
final PsiElement value = ((AssignmentExpression) expression).getValue();
if (value instanceof PhpTypedElement) {
result = resolveType((PhpTypedElement) value, project);
}
} else if (expression instanceof FieldReference) {
final FieldReference reference = (FieldReference) expression;
final String referenceName = reference.getName();
if (referenceName != null && !referenceName.isEmpty()) {
final PsiElement base = reference.getClassReference();
if (base instanceof Variable && ((Variable) base).getName().equals("this")) {
final PsiElement resolvedField = OpenapiResolveUtil.resolveReference(reference);
if (resolvedField instanceof Field) {
final PhpType declaredType = resolveDeclaredType((Field) resolvedField);
if (!declaredType.isEmpty()) {
result = declaredType;
}
}
}
}
} else if (expression instanceof ParenthesizedExpression) {
final PsiElement value = ((ParenthesizedExpression) expression).getArgument();
if (value instanceof PhpTypedElement) {
result = resolveType((PhpTypedElement) value, project);
}
} else if (expression instanceof Parameter) {
/* Incorrect type inference for variadic parameters */
final Parameter parameter = (Parameter) expression;
if (parameter.isVariadic()) {
final PhpType resolved = parameter.getDeclaredType();
if (resolved.isEmpty()) {
result = new PhpType().add(PhpType.ARRAY);
} else if (resolved.size() == 1 && !Types.getType(resolved.getTypes().iterator().next()).equals(Types.strArray)) {
result = new PhpType().add(String.format("%s[]", resolved.getTypes().iterator().next()));
}
}
}
/* default behaviour */
result = result == null ? expression.getType().global(project) : result;
} catch (final Throwable error) {
if (error instanceof ProcessCanceledException) {
throw error;
}
result = null;
}
return result;
}
use of com.jetbrains.php.lang.psi.resolve.types.PhpType in project phpinspectionsea by kalessil.
the class MissingIssetImplementationInspector method buildVisitor.
@Override
@NotNull
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
return new BasePhpElementVisitor() {
@Override
public void visitPhpEmpty(@NotNull PhpEmpty expression) {
this.analyzeDispatchedExpressions(expression.getVariables());
}
@Override
public void visitPhpIsset(@NotNull PhpIsset expression) {
this.analyzeDispatchedExpressions(expression.getVariables());
}
private void analyzeDispatchedExpressions(@NotNull PhpExpression[] parameters) {
final Project project = holder.getProject();
final PhpIndex projectIndex = PhpIndex.getInstance(project);
for (final PhpExpression parameter : parameters) {
if (parameter instanceof FieldReference) {
final FieldReference reference = (FieldReference) parameter;
final ASTNode nameNode = reference.getNameNode();
final String parameterName = parameter.getName();
/* if the field name is not implicit or the field resolved, continue */
if ((nameNode == null || nameNode instanceof Variable) || (parameterName == null || parameterName.isEmpty()) || !OpenapiTypesUtil.is(OpenapiPsiSearchUtil.findResolutionOperator(reference), PhpTokenTypes.ARROW) || OpenapiResolveUtil.resolveReference(reference) != null) {
continue;
}
/* false-positives: in the $this context we are dealing with dynamic properties */
final PhpExpression variable = reference.getClassReference();
if (null == variable || variable.getText().equals("$this")) {
continue;
}
/* long way around: identify an lookup classes */
final Set<String> resolvedTypes = new HashSet<>();
final PhpType resolved = OpenapiResolveUtil.resolveType(variable, project);
if (resolved != null) {
resolved.filterUnknown().getTypes().forEach(t -> resolvedTypes.add(Types.getType(t)));
}
for (final String type : resolvedTypes) {
/* false-positives: SimpleXMLElement, stdClass */
if (type.startsWith("\\") && !magicClasses.contains(type)) {
final Collection<PhpClass> classes = projectIndex.getClassesByFQN(type);
final PhpClass clazz = classes.isEmpty() ? null : classes.iterator().next();
/* resolved class FQN might differ from what type states */
if (clazz != null && !magicClasses.contains(clazz.getFQN())) {
final boolean hasField = OpenapiResolveUtil.resolveField(clazz, parameterName) != null;
if (!hasField && OpenapiResolveUtil.resolveMethod(clazz, "__isset") == null) {
holder.registerProblem(parameter, MessagesPresentationUtil.prefixWithEa(messagePattern.replace("%c%", type)), ProblemHighlightType.GENERIC_ERROR);
break;
}
}
}
}
resolvedTypes.clear();
}
}
}
};
}
Aggregations