PyType (@NotNull TypeEvalContext context) { final Set<String> usedAttributes = new LinkedHashSet<>(); final ScopeOwner owner = ScopeUtil.getScopeOwner(this); final String name = getName(); final Ref<Boolean> parameterWasReassigned = Ref.create(false); final Ref<Boolean> noneComparison = Ref.create(false); if (owner != null && name != null) { owner.accept(new PyRecursiveElementVisitor() { @Override public void visitPyElement(@NotNull PyElement node) { if (parameterWasReassigned.get()) return; if (node instanceof ScopeOwner && node != owner) { return; } if (node instanceof PyQualifiedExpression expr) { final PyExpression qualifier = expr.getQualifier(); if (qualifier != null) { final String attributeName = expr.getReferencedName(); final PyExpression referencedExpr = node instanceof PyBinaryExpression && PyNames.isRightOperatorName(attributeName) ? ((PyBinaryExpression)node).getRightExpression() : qualifier; if (attributeName != null && isReferenceToParameter(referencedExpr)) { usedAttributes.add(attributeName); } } else if (isReferenceToParameter(expr)) { StreamEx.of(getParametersByCallArgument(expr, context)) .nonNull() .map(parameter -> parameter.getType(context)) .select(PyStructuralType.class) .forEach(type -> usedAttributes.addAll(type.getAttributeNames())); } } super.visitPyElement(node); } @Override public void visitPyIfStatement(@NotNull PyIfStatement node) { if (parameterWasReassigned.get()) return; final PyExpression ifCondition = node.getIfPart().getCondition(); if (ifCondition != null) { ifCondition.accept(this); } for (PyIfPart part : node.getElifParts()) { final PyExpression elseIfCondition = part.getCondition(); if (elseIfCondition != null) { elseIfCondition.accept(this); } } } @Override public void visitPyCallExpression(@NotNull PyCallExpression node) { if (parameterWasReassigned.get()) return; Optional .ofNullable(node.getCallee()) .filter(callee -> "len".equals(callee.getName()) && isReferenceToParameter(node.getArgument(0, PyReferenceExpression.class))) .map(PyExpression::getReference) .map(PsiReference::resolve) .filter(element -> PyBuiltinCache.getInstance(element).isBuiltin(element)) .ifPresent(callable -> usedAttributes.add(PyNames.LEN)); super.visitPyCallExpression(node); } @Override public void visitPyForStatement(@NotNull PyForStatement node) { if (parameterWasReassigned.get()) return; if (isReferenceToParameter(node.getForPart().getSource())) { usedAttributes.add(PyNames.ITER); } super.visitPyForStatement(node); } @Override public void visitPyTargetExpression(@NotNull PyTargetExpression node) { if (parameterWasReassigned.get()) return; if (isReferenceToParameter(node)) { parameterWasReassigned.set(true); } else { super.visitPyTargetExpression(node); } } @Override public void visitPyBinaryExpression(@NotNull PyBinaryExpression node) { super.visitPyBinaryExpression(node); if (noneComparison.get() || !node.isOperator(PyNames.IS) && !node.isOperator("isnot")) return; final PyExpression lhs = node.getLeftExpression(); final PyExpression rhs = node.getRightExpression(); if (isReferenceToParameter(lhs) ^ isReferenceToParameter(rhs) && (lhs != null && context.getType(lhs) instanceof PyNoneType) ^ (rhs != null && context.getType(rhs) instanceof PyNoneType)) { noneComparison.set(true); } } @Contract("null -> false") private boolean isReferenceToParameter(@Nullable PsiElement element) { if (element == null) return false; final PsiReference reference = element.getReference(); return reference != null && reference.isReferenceTo(PyNamedParameterImpl.this); } }); } if (!usedAttributes.isEmpty()) { final PyStructuralType structuralType = new PyStructuralType(usedAttributes, true); return noneComparison.get() ? PyUnionType.union(structuralType, PyNoneType.INSTANCE) : structuralType; } return null; }