Skeletons sync
[idea/community.git] / python / src / com / jetbrains / python / psi / impl / PyTargetExpressionImpl.java
1 /*
2  * Copyright 2000-2014 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.jetbrains.python.psi.impl;
17
18 import com.intellij.lang.ASTNode;
19 import com.intellij.navigation.ItemPresentation;
20 import com.intellij.openapi.extensions.Extensions;
21 import com.intellij.openapi.util.Pair;
22 import com.intellij.psi.PsiElement;
23 import com.intellij.psi.PsiPolyVariantReference;
24 import com.intellij.psi.PsiReference;
25 import com.intellij.psi.search.GlobalSearchScope;
26 import com.intellij.psi.search.LocalSearchScope;
27 import com.intellij.psi.search.SearchScope;
28 import com.intellij.psi.stubs.IStubElementType;
29 import com.intellij.psi.stubs.StubElement;
30 import com.intellij.psi.util.PsiTreeUtil;
31 import com.intellij.psi.util.QualifiedName;
32 import com.intellij.util.IncorrectOperationException;
33 import com.intellij.util.PlatformIcons;
34 import com.jetbrains.python.PyElementTypes;
35 import com.jetbrains.python.PyNames;
36 import com.jetbrains.python.PyTokenTypes;
37 import com.jetbrains.python.PythonDialectsTokenSetProvider;
38 import com.jetbrains.python.codeInsight.controlflow.ControlFlowCache;
39 import com.jetbrains.python.codeInsight.controlflow.ScopeOwner;
40 import com.jetbrains.python.codeInsight.dataflow.scope.Scope;
41 import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil;
42 import com.jetbrains.python.documentation.docstrings.DocStringUtil;
43 import com.jetbrains.python.psi.*;
44 import com.jetbrains.python.psi.impl.references.PyQualifiedReference;
45 import com.jetbrains.python.psi.impl.references.PyTargetReference;
46 import com.jetbrains.python.psi.impl.stubs.CustomTargetExpressionStub;
47 import com.jetbrains.python.psi.resolve.PyResolveContext;
48 import com.jetbrains.python.psi.resolve.QualifiedNameFinder;
49 import com.jetbrains.python.psi.resolve.RatedResolveResult;
50 import com.jetbrains.python.psi.stubs.PyClassStub;
51 import com.jetbrains.python.psi.stubs.PyFunctionStub;
52 import com.jetbrains.python.psi.stubs.PyTargetExpressionStub;
53 import com.jetbrains.python.psi.types.*;
54 import org.jetbrains.annotations.NotNull;
55 import org.jetbrains.annotations.Nullable;
56
57 import javax.swing.*;
58 import java.util.ArrayList;
59 import java.util.Collection;
60 import java.util.Collections;
61 import java.util.List;
62
63 /**
64  * @author yole
65  */
66 public class PyTargetExpressionImpl extends PyBaseElementImpl<PyTargetExpressionStub> implements PyTargetExpression {
67   QualifiedName myQualifiedName;
68
69   public PyTargetExpressionImpl(ASTNode astNode) {
70     super(astNode);
71   }
72
73   public PyTargetExpressionImpl(final PyTargetExpressionStub stub) {
74     super(stub, PyElementTypes.TARGET_EXPRESSION);
75   }
76
77   public PyTargetExpressionImpl(final PyTargetExpressionStub stub, IStubElementType nodeType) {
78     super(stub, nodeType);
79   }
80
81   @Override
82   protected void acceptPyVisitor(PyElementVisitor pyVisitor) {
83     pyVisitor.visitPyTargetExpression(this);
84   }
85
86   @Nullable
87   @Override
88   public String getName() {
89     final PyTargetExpressionStub stub = getStub();
90     if (stub != null) {
91       return stub.getName();
92     }
93     ASTNode node = getNameElement();
94     return node != null ? node.getText() : null;
95   }
96
97   @Override
98   public int getTextOffset() {
99     final ASTNode nameElement = getNameElement();
100     return nameElement != null ? nameElement.getStartOffset() : getTextRange().getStartOffset();
101   }
102
103   @Nullable
104   public ASTNode getNameElement() {
105     return getNode().findChildByType(PyTokenTypes.IDENTIFIER);
106   }
107
108   public PsiElement getNameIdentifier() {
109     final ASTNode nameElement = getNameElement();
110     return nameElement == null ? null : nameElement.getPsi();
111   }
112
113   public String getReferencedName() {
114     return getName();
115   }
116
117   public PsiElement setName(@NotNull String name) throws IncorrectOperationException {
118     final ASTNode oldNameElement = getNameElement();
119     if (oldNameElement != null) {
120       final ASTNode nameElement = PyUtil.createNewName(this, name);
121       getNode().replaceChild(oldNameElement, nameElement);
122     }
123     return this;
124   }
125
126   public PyType getType(@NotNull TypeEvalContext context, @NotNull TypeEvalContext.Key key) {
127     if (!TypeEvalStack.mayEvaluate(this)) {
128       return null;
129     }
130     try {
131       if (PyNames.ALL.equals(getName())) {
132         // no type for __all__, to avoid unresolved reference errors for expressions where a qualifier is a name
133         // imported via __all__
134         return null;
135       }
136       final PyType pyType = PyReferenceExpressionImpl.getReferenceTypeFromProviders(this, context, null);
137       if (pyType != null) {
138         return pyType;
139       }
140       PyType type = getTypeFromDocString();
141       if (type != null) {
142         return type;
143       }
144       if (!context.maySwitchToAST(this)) {
145         final PsiElement value = resolveAssignedValue(PyResolveContext.noImplicits().withTypeEvalContext(context));
146         if (value instanceof PyTypedElement) {
147           type = context.getType((PyTypedElement)value);
148           if (type instanceof PyNoneType) {
149             return null;
150           }
151           return type;
152         }
153         return null;
154       }
155       type = getTypeFromComment(this);
156       if (type != null) {
157         return type;
158       }
159       final PsiElement parent = getParent();
160       if (parent instanceof PyAssignmentStatement) {
161         final PyAssignmentStatement assignmentStatement = (PyAssignmentStatement)parent;
162         PyExpression assignedValue = assignmentStatement.getAssignedValue();
163         if (assignedValue instanceof PyParenthesizedExpression) {
164           assignedValue = ((PyParenthesizedExpression)assignedValue).getContainedExpression();
165         }
166         if (assignedValue != null) {
167           if (assignedValue instanceof PyYieldExpression) {
168             return null;
169           }
170           return context.getType(assignedValue);
171         }
172       }
173       if (parent instanceof PyTupleExpression) {
174         PsiElement nextParent = parent.getParent();
175         while (nextParent instanceof PyParenthesizedExpression) {
176           nextParent = nextParent.getParent();
177         }
178         if (nextParent instanceof PyAssignmentStatement) {
179           final PyAssignmentStatement assignment = (PyAssignmentStatement)nextParent;
180           final PyExpression value = assignment.getAssignedValue();
181           if (value != null) {
182             final PyType assignedType = PyTypeChecker.toNonWeakType(context.getType(value), context);
183             if (assignedType instanceof PyTupleType) {
184               final PyType t = PyTypeChecker.getTargetTypeFromTupleAssignment(this, (PyTupleExpression)parent, (PyTupleType)assignedType);
185               if (t != null) {
186                 return t;
187               }
188             }
189           }
190         }
191       }
192       if (parent instanceof PyWithItem) {
193         return getWithItemVariableType(context, (PyWithItem)parent);
194       }
195       PyType iterType = getTypeFromIteration(context);
196       if (iterType != null) {
197         return iterType;
198       }
199       PyType excType = getTypeFromExcept();
200       if (excType != null) {
201         return excType;
202       }
203       return null;
204     }
205     finally {
206       TypeEvalStack.evaluated(this);
207     }
208   }
209
210   @Nullable
211   private static PyType getWithItemVariableType(TypeEvalContext context, PyWithItem item) {
212     final PyExpression expression = item.getExpression();
213     if (expression != null) {
214       final PyType exprType = context.getType(expression);
215       if (exprType instanceof PyClassType) {
216         final PyClass cls = ((PyClassType)exprType).getPyClass();
217         final PyFunction enter = cls.findMethodByName(PyNames.ENTER, true, null);
218         if (enter != null) {
219           final PyType enterType = enter.getCallType(expression, Collections.<PyExpression, PyNamedParameter>emptyMap(), context);
220           if (enterType != null) {
221             return enterType;
222           }
223           for (PyTypeProvider provider: Extensions.getExtensions(PyTypeProvider.EP_NAME)) {
224             PyType typeFromProvider = provider.getContextManagerVariableType(cls, expression, context);
225             if (typeFromProvider != null) {
226               return typeFromProvider;
227             }
228           }
229           // Guess the return type of __enter__
230           return PyUnionType.createWeakType(exprType);
231         }
232       }
233     }
234     return null;
235   }
236
237   @Nullable
238   public PyType getTypeFromDocString() {
239     String typeName = null;
240     final String name = getName();
241     final StructuredDocString targetDocString = getStructuredDocString();
242     if (targetDocString != null) {
243       typeName = targetDocString.getParamType(null);
244       if (typeName == null) {
245         typeName = targetDocString.getParamType(name);
246       }
247     }
248     if (typeName == null && PyUtil.isAttribute(this)) {
249       final PyClass cls = getContainingClass();
250       if (cls != null) {
251         final StructuredDocString classDocString = cls.getStructuredDocString();
252         if (classDocString != null) {
253           typeName = classDocString.getParamType(name);
254         }
255       }
256     }
257     if (typeName != null) {
258       return PyTypeParser.getTypeByName(this, typeName);
259     }
260     return null;
261   }
262
263   @Nullable
264   public static PyType getTypeFromComment(PyTargetExpressionImpl targetExpression) {
265     String docComment = DocStringUtil.getAttributeDocComment(targetExpression);
266     if (docComment != null) {
267       StructuredDocString structuredDocString = DocStringUtil.parse(docComment, targetExpression);
268       String typeName = structuredDocString.getParamType(null);
269       if (typeName == null) {
270         typeName = structuredDocString.getParamType(targetExpression.getName());
271       }
272       if (typeName != null) {
273         return PyTypeParser.getTypeByName(targetExpression, typeName);
274       }
275     }
276     return null;
277   }
278
279   @Nullable
280   private PyType getTypeFromIteration(@NotNull TypeEvalContext context) {
281     PyExpression target = null;
282     PyExpression source = null;
283     final PyForPart forPart = PsiTreeUtil.getParentOfType(this, PyForPart.class);
284     if (forPart != null) {
285       final PyExpression expr = forPart.getTarget();
286       if (PsiTreeUtil.isAncestor(expr, this, false)) {
287         target = expr;
288         source = forPart.getSource();
289       }
290     }
291     final PyComprehensionElement comprh = PsiTreeUtil.getParentOfType(this, PyComprehensionElement.class);
292     if (comprh != null) {
293       for (ComprhForComponent c : comprh.getForComponents()) {
294         final PyExpression expr = c.getIteratorVariable();
295         if (PsiTreeUtil.isAncestor(expr, this, false)) {
296           target = expr;
297           source = c.getIteratedList();
298         }
299       }
300     }
301     if (source != null) {
302       final PyType sourceType = context.getType(source);
303       final PyType type = getIterationType(sourceType, source, this, context);
304       if (type instanceof PyTupleType && target instanceof PyTupleExpression) {
305         return PyTypeChecker.getTargetTypeFromTupleAssignment(this, (PyTupleExpression)target, (PyTupleType)type);
306       }
307       if (target == this && type != null) {
308         return type;
309       }
310     }
311     return null;
312   }
313
314   @Nullable
315   private static PyType getIterationType(@Nullable PyType iterableType, @Nullable PyExpression source, @NotNull PsiElement anchor,
316                                          @NotNull TypeEvalContext context) {
317     if (iterableType instanceof PyTupleType) {
318       final PyTupleType tupleType = (PyTupleType)iterableType;
319       final List<PyType> memberTypes = new ArrayList<PyType>();
320       for (int i = 0; i < tupleType.getElementCount(); i++) {
321         memberTypes.add(tupleType.getElementType(i));
322       }
323       return PyUnionType.union(memberTypes);
324     }
325     else if (iterableType instanceof PyUnionType) {
326       final Collection<PyType> members = ((PyUnionType)iterableType).getMembers();
327       final List<PyType> iterationTypes = new ArrayList<PyType>();
328       for (PyType member : members) {
329         iterationTypes.add(getIterationType(member, source, anchor, context));
330       }
331       return PyUnionType.union(iterationTypes);
332     }
333     else if (iterableType != null && PyABCUtil.isSubtype(iterableType, PyNames.ITERABLE, context)) {
334       final PyFunction iterateMethod = findMethodByName(iterableType, PyNames.ITER, context);
335       if (iterateMethod != null) {
336         final PyType iterateReturnType = getContextSensitiveType(iterateMethod, context, source);
337         final PyType type = getCollectionElementType(iterateReturnType, context);
338         if (!isTrivialType(type)) {
339           return type;
340         }
341       }
342       final String nextMethodName = LanguageLevel.forElement(anchor).isAtLeast(LanguageLevel.PYTHON30) ?
343                                     PyNames.DUNDER_NEXT : PyNames.NEXT;
344       final PyFunction next = findMethodByName(iterableType, nextMethodName, context);
345       if (next != null) {
346         final PyType type = getContextSensitiveType(next, context, source);
347         if (!isTrivialType(type)) {
348           return type;
349         }
350       }
351       final PyFunction getItem = findMethodByName(iterableType, PyNames.GETITEM, context);
352       if (getItem != null) {
353         final PyType type = getContextSensitiveType(getItem, context, source);
354         if (!isTrivialType(type)) {
355           return type;
356         }
357       }
358     }
359     return null;
360   }
361
362   private static boolean isTrivialType(@Nullable PyType type) {
363     return type == null || type instanceof PyNoneType;
364   }
365
366   @Nullable
367   private static PyType getCollectionElementType(@Nullable PyType type, @NotNull TypeEvalContext context) {
368     if (type instanceof PyCollectionType) {
369       final List<PyType> elementTypes = ((PyCollectionType)type).getElementTypes(context);
370       // TODO: Select the parameter type that matches T in Iterable[T]
371       return elementTypes.isEmpty() ? null : elementTypes.get(0);
372     }
373     return null;
374   }
375
376   @Nullable
377   private static PyFunction findMethodByName(@NotNull PyType type, @NotNull String name, @NotNull TypeEvalContext context) {
378     final PyResolveContext resolveContext = PyResolveContext.defaultContext().withTypeEvalContext(context);
379     final List<? extends RatedResolveResult> results = type.resolveMember(name, null, AccessDirection.READ, resolveContext);
380     if (results != null && !results.isEmpty()) {
381       final RatedResolveResult result = results.get(0);
382       final PsiElement element = result.getElement();
383       if (element instanceof PyFunction) {
384         return (PyFunction)element;
385       }
386     }
387     return null;
388   }
389
390   @Nullable
391   private static PyType getContextSensitiveType(@NotNull PyFunction function, @NotNull TypeEvalContext context,
392                                                 @Nullable PyExpression source) {
393     return function.getCallType(source, Collections.<PyExpression, PyNamedParameter>emptyMap(), context);
394   }
395
396   @Nullable
397   private PyType getTypeFromExcept() {
398     PyExceptPart exceptPart = PsiTreeUtil.getParentOfType(this, PyExceptPart.class);
399     if (exceptPart == null || exceptPart.getTarget() != this) {
400       return null;
401     }
402     final PyExpression exceptClass = exceptPart.getExceptClass();
403     if (exceptClass instanceof PyReferenceExpression) {
404       final PsiElement element = ((PyReferenceExpression)exceptClass).getReference().resolve();
405       if (element instanceof PyClass) {
406         return new PyClassTypeImpl((PyClass) element, false);
407       }
408     }
409     return null;
410   }
411
412   public PyExpression getQualifier() {
413     ASTNode qualifier = getNode().findChildByType(PythonDialectsTokenSetProvider.INSTANCE.getExpressionTokens());
414     return qualifier != null ? (PyExpression) qualifier.getPsi() : null;
415   }
416
417   @Nullable
418   @Override
419   public QualifiedName asQualifiedName() {
420     if (myQualifiedName == null) {
421       myQualifiedName = PyPsiUtils.asQualifiedName(this);
422     }
423     return myQualifiedName;
424   }
425
426   public String toString() {
427     return super.toString() + ": " + getName();
428   }
429
430   public Icon getIcon(final int flags) {
431     if (isQualified() || PsiTreeUtil.getStubOrPsiParentOfType(this, PyDocStringOwner.class) instanceof PyClass) {
432       return PlatformIcons.FIELD_ICON;
433     }
434     return PlatformIcons.VARIABLE_ICON;
435   }
436
437   public boolean isQualified() {
438     PyTargetExpressionStub stub = getStub();
439     if (stub != null) {
440       return stub.isQualified();
441     }
442     return getQualifier() != null;
443   }
444
445   @Nullable
446   @Override
447   public PsiElement resolveAssignedValue(@NotNull PyResolveContext resolveContext) {
448     final TypeEvalContext context = resolveContext.getTypeEvalContext();
449     if (context.maySwitchToAST(this)) {
450       final PyExpression value = findAssignedValue();
451       if (value != null) {
452         final List<PsiElement> results = PyUtil.multiResolveTopPriority(value, resolveContext);
453         return !results.isEmpty() ? results.get(0) : null;
454       }
455       return null;
456     }
457     else {
458       final QualifiedName qName = getAssignedQName();
459       if (qName != null) {
460         final ScopeOwner owner = ScopeUtil.getScopeOwner(this);
461         if (owner instanceof PyTypedElement) {
462           final List<String> components = qName.getComponents();
463           if (!components.isEmpty()) {
464             PsiElement resolved = owner;
465             for (String component : components) {
466               if (!(resolved instanceof PyTypedElement)) {
467                 return null;
468               }
469               final PyType qualifierType = context.getType((PyTypedElement)resolved);
470               if (qualifierType == null) {
471                 return null;
472               }
473               final List<? extends RatedResolveResult> results = qualifierType.resolveMember(component, null, AccessDirection.READ,
474                                                                                              resolveContext);
475               if (results == null || results.isEmpty()) {
476                 return null;
477               }
478               resolved = results.get(0).getElement();
479             }
480             return resolved;
481           }
482         }
483       }
484       return null;
485     }
486   }
487
488   @Nullable
489   @Override
490   public PyExpression findAssignedValue() {
491     if (isValid()) {
492       PyAssignmentStatement assignment = PsiTreeUtil.getParentOfType(this, PyAssignmentStatement.class);
493       if (assignment != null) {
494         List<Pair<PyExpression, PyExpression>> mapping = assignment.getTargetsToValuesMapping();
495         for (Pair<PyExpression, PyExpression> pair : mapping) {
496           PyExpression assigned_to = pair.getFirst();
497           if (assigned_to == this) return pair.getSecond();
498         }
499       }
500     }
501     return null;
502   }
503
504   @Nullable
505   @Override
506   public QualifiedName getAssignedQName() {
507     final PyTargetExpressionStub stub = getStub();
508     if (stub != null) {
509       if (stub.getInitializerType() == PyTargetExpressionStub.InitializerType.ReferenceExpression) {
510         return stub.getInitializer();
511       }
512       return null;
513     }
514     return PyPsiUtils.asQualifiedName(findAssignedValue());
515   }
516
517   @Override
518   public QualifiedName getCalleeName() {
519     final PyTargetExpressionStub stub = getStub();
520     if (stub != null) {
521       final PyTargetExpressionStub.InitializerType initializerType = stub.getInitializerType();
522       if (initializerType == PyTargetExpressionStub.InitializerType.CallExpression) {
523         return stub.getInitializer();
524       }
525       else if (initializerType == PyTargetExpressionStub.InitializerType.Custom) {
526         final CustomTargetExpressionStub customStub = stub.getCustomStub(CustomTargetExpressionStub.class);
527         if (customStub != null) {
528           return customStub.getCalleeName();
529         }
530       }
531       return null;
532     }
533     final PyExpression value = findAssignedValue();
534     if (value instanceof PyCallExpression) {
535       final PyExpression callee = ((PyCallExpression)value).getCallee();
536       return PyPsiUtils.asQualifiedName(callee);
537     }
538     return null;
539   }
540
541   @NotNull
542   @Override
543   public PsiReference getReference() {
544     return getReference(PyResolveContext.defaultContext());
545   }
546
547   @NotNull
548   @Override
549   public PsiPolyVariantReference getReference(final PyResolveContext resolveContext) {
550     if (isQualified()) {
551       return new PyQualifiedReference(this, resolveContext);
552     }
553     return new PyTargetReference(this, resolveContext);
554   }
555
556   @NotNull
557   @Override
558   public SearchScope getUseScope() {
559     if (isQualified()) {
560       return super.getUseScope();
561     }
562     final ScopeOwner owner = ScopeUtil.getScopeOwner(this);
563     if (owner != null) {
564       final Scope scope = ControlFlowCache.getScope(owner);
565       if (scope.isGlobal(getName())) {
566         return GlobalSearchScope.projectScope(getProject());
567       }
568       if (scope.isNonlocal(getName())) {
569         return new LocalSearchScope(getContainingFile());
570       }
571     }
572
573     // find highest level function containing our var
574     PyElement container = this;
575     while(true) {
576       PyElement parentContainer = PsiTreeUtil.getParentOfType(container, PyFunction.class, PyClass.class);
577       if (parentContainer instanceof PyClass) {
578         if (isQualified()) {
579           return super.getUseScope();
580         }
581         break;
582       }
583       if (parentContainer == null) {
584         break;
585       }
586       container = parentContainer;
587     }
588     if (container instanceof PyFunction) {
589       return new LocalSearchScope(container);
590     }
591     return super.getUseScope();
592   }
593
594   @Override
595   public PyClass getContainingClass() {
596     final PyTargetExpressionStub stub = getStub();
597     if (stub != null) {
598       final StubElement parentStub = stub.getParentStub();
599       if (parentStub instanceof PyClassStub) {
600         return ((PyClassStub)parentStub).getPsi();
601       }
602       if (parentStub instanceof PyFunctionStub) {
603         final StubElement functionParent = parentStub.getParentStub();
604         if (functionParent instanceof PyClassStub) {
605           return ((PyClassStub) functionParent).getPsi();
606         }
607       }
608
609       return null;
610     }
611
612     final PsiElement parent = PsiTreeUtil.getParentOfType(this, PyFunction.class, PyClass.class);
613     if (parent instanceof PyClass) {
614       return (PyClass)parent;
615     }
616     if (parent instanceof PyFunction) {
617       return ((PyFunction)parent).getContainingClass();
618     }
619     return null;
620   }
621
622   @Override
623   public ItemPresentation getPresentation() {
624     return new PyElementPresentation(this) {
625       @Nullable
626       @Override
627       public String getLocationString() {
628         final PyClass containingClass = getContainingClass();
629         if (containingClass != null) {
630           return "(" + containingClass.getName() + " in " + getPackageForFile(getContainingFile()) + ")";
631         }
632         return super.getLocationString();
633       }
634     };
635   }
636
637   @Nullable
638   @Override
639   public String getDocStringValue() {
640     final PyTargetExpressionStub stub = getStub();
641     if (stub != null) {
642       return stub.getDocString();
643     }
644     return DocStringUtil.getDocStringValue(this);
645   }
646
647   @Nullable
648   @Override
649   public StructuredDocString getStructuredDocString() {
650     return DocStringUtil.getStructuredDocString(this);
651   }
652
653   @Nullable
654   @Override
655   public PyStringLiteralExpression getDocStringExpression() {
656     final PsiElement parent = getParent();
657     if (parent instanceof PyAssignmentStatement) {
658       final PsiElement nextSibling = PyPsiUtils.getNextNonCommentSibling(parent, true);
659       if (nextSibling instanceof PyExpressionStatement) {
660         final PyExpression expression = ((PyExpressionStatement)nextSibling).getExpression();
661         if (expression instanceof PyStringLiteralExpression) {
662           return (PyStringLiteralExpression)expression;
663         }
664       }
665     }
666     return null;
667   }
668
669   @Override
670   public void subtreeChanged() {
671     super.subtreeChanged();
672     myQualifiedName = null;
673   }
674
675   @Nullable
676   @Override
677   public String getQualifiedName() {
678     return QualifiedNameFinder.getQualifiedName(this);
679   }
680 }