001    /*
002     * Copyright 2010-2013 JetBrains s.r.o.
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     * http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    
017    package org.jetbrains.jet.lang.psi;
018    
019    import com.google.common.base.Function;
020    import com.google.common.base.Predicate;
021    import com.google.common.collect.Lists;
022    import com.intellij.lang.ASTNode;
023    import com.intellij.openapi.util.Condition;
024    import com.intellij.psi.PsiComment;
025    import com.intellij.psi.PsiElement;
026    import com.intellij.psi.PsiFile;
027    import com.intellij.psi.PsiWhiteSpace;
028    import com.intellij.psi.impl.CheckUtil;
029    import com.intellij.psi.impl.source.codeStyle.CodeEditUtil;
030    import com.intellij.psi.tree.IElementType;
031    import com.intellij.psi.util.PsiTreeUtil;
032    import com.intellij.util.codeInsight.CommentUtilCore;
033    import com.intellij.util.containers.ContainerUtil;
034    import org.jetbrains.annotations.NotNull;
035    import org.jetbrains.annotations.Nullable;
036    import org.jetbrains.jet.JetNodeTypes;
037    import org.jetbrains.jet.kdoc.psi.api.KDocElement;
038    import org.jetbrains.jet.lang.parsing.JetExpressionParsing;
039    import org.jetbrains.jet.lang.psi.psiUtil.PsiUtilPackage;
040    import org.jetbrains.jet.lang.resolve.ImportPath;
041    import org.jetbrains.jet.lang.resolve.name.FqName;
042    import org.jetbrains.jet.lang.resolve.name.FqNameUnsafe;
043    import org.jetbrains.jet.lang.resolve.name.Name;
044    import org.jetbrains.jet.lang.resolve.name.SpecialNames;
045    import org.jetbrains.jet.lang.types.expressions.OperatorConventions;
046    import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns;
047    import org.jetbrains.jet.lexer.JetToken;
048    import org.jetbrains.jet.lexer.JetTokens;
049    
050    import java.util.Collection;
051    import java.util.HashSet;
052    import java.util.List;
053    import java.util.Set;
054    
055    public class JetPsiUtil {
056        private JetPsiUtil() {
057        }
058    
059        public interface JetExpressionWrapper {
060            JetExpression getBaseExpression();
061        }
062    
063        public static <D> void visitChildren(@NotNull JetElement element, @NotNull JetTreeVisitor<D> visitor, D data) {
064            PsiElement child = element.getFirstChild();
065            while (child != null) {
066                if (child instanceof JetElement) {
067                    ((JetElement) child).accept(visitor, data);
068                }
069                child = child.getNextSibling();
070            }
071        }
072    
073        @NotNull
074        public static JetExpression safeDeparenthesize(@NotNull JetExpression expression, boolean deparenthesizeBinaryExpressionWithTypeRHS) {
075            JetExpression deparenthesized = deparenthesize(expression, deparenthesizeBinaryExpressionWithTypeRHS);
076            return deparenthesized != null ? deparenthesized : expression;
077        }
078    
079        @Nullable
080        public static JetExpression deparenthesize(@Nullable JetExpression expression) {
081            return deparenthesize(expression, true);
082        }
083    
084        @Nullable
085        public static JetExpression deparenthesize(
086                @Nullable JetExpression expression,
087                boolean deparenthesizeBinaryExpressionWithTypeRHS
088        ) {
089            return deparenthesizeWithResolutionStrategy(expression, deparenthesizeBinaryExpressionWithTypeRHS, null);
090        }
091    
092        @Nullable
093        @Deprecated //Use JetPsiUtil.deparenthesize() or ExpressionTypingServices.deparenthesize()
094        public static JetExpression deparenthesizeWithResolutionStrategy(
095                @Nullable JetExpression expression,
096                boolean deparenthesizeBinaryExpressionWithTypeRHS,
097                @Nullable Function<JetTypeReference, Void> typeResolutionStrategy
098        ) {
099            if (deparenthesizeBinaryExpressionWithTypeRHS && expression instanceof JetBinaryExpressionWithTypeRHS) {
100                JetBinaryExpressionWithTypeRHS binaryExpression = (JetBinaryExpressionWithTypeRHS) expression;
101                JetSimpleNameExpression operationSign = binaryExpression.getOperationReference();
102                if (JetTokens.COLON.equals(operationSign.getReferencedNameElementType())) {
103                    expression = binaryExpression.getLeft();
104                    JetTypeReference typeReference = binaryExpression.getRight();
105                    if (typeResolutionStrategy != null && typeReference != null) {
106                        typeResolutionStrategy.apply(typeReference);
107                    }
108                }
109            }
110            else if (expression instanceof JetPrefixExpression) {
111                JetExpression baseExpression = getBaseExpressionIfLabeledExpression((JetPrefixExpression) expression);
112                if (baseExpression != null) {
113                    expression = baseExpression;
114                }
115            }
116            else if (expression instanceof JetExpressionWrapper) {
117                expression = ((JetExpressionWrapper) expression).getBaseExpression();
118            }
119            if (expression instanceof JetParenthesizedExpression) {
120                JetExpression innerExpression = ((JetParenthesizedExpression) expression).getExpression();
121                return innerExpression != null ? deparenthesizeWithResolutionStrategy(
122                        innerExpression, deparenthesizeBinaryExpressionWithTypeRHS, typeResolutionStrategy) : null;
123            }
124            return expression;
125        }
126    
127        @Nullable
128        public static JetExpression getBaseExpressionIfLabeledExpression(@NotNull JetPrefixExpression expression) {
129            if (isLabeledExpression(expression)) {
130                return expression.getBaseExpression();
131            }
132            return null;
133        }
134    
135        public static boolean isLabeledExpression(JetPrefixExpression expression) {
136            return JetTokens.LABELS.contains(expression.getOperationReference().getReferencedNameElementType());
137        }
138    
139        @NotNull
140        public static Name safeName(@Nullable String name) {
141            return name == null ? SpecialNames.NO_NAME_PROVIDED : Name.identifier(name);
142        }
143    
144        @NotNull
145        public static Set<JetElement> findRootExpressions(@NotNull Collection<JetElement> unreachableElements) {
146            Set<JetElement> rootElements = new HashSet<JetElement>();
147            final Set<JetElement> shadowedElements = new HashSet<JetElement>();
148            JetVisitorVoid shadowAllChildren = new JetVisitorVoid() {
149                @Override
150                public void visitJetElement(@NotNull JetElement element) {
151                    if (shadowedElements.add(element)) {
152                        element.acceptChildren(this);
153                    }
154                }
155            };
156    
157            for (JetElement element : unreachableElements) {
158                if (shadowedElements.contains(element)) continue;
159                element.acceptChildren(shadowAllChildren);
160    
161                rootElements.removeAll(shadowedElements);
162                rootElements.add(element);
163            }
164            return rootElements;
165        }
166    
167        @NotNull
168        public static String unquoteIdentifier(@NotNull String quoted) {
169            if (quoted.indexOf('`') < 0) {
170                return quoted;
171            }
172    
173            if (quoted.startsWith("`") && quoted.endsWith("`") && quoted.length() >= 2) {
174                return quoted.substring(1, quoted.length() - 1);
175            }
176            else {
177                return quoted;
178            }
179        }
180    
181        @NotNull
182        public static String unquoteIdentifierOrFieldReference(@NotNull String quoted) {
183            if (quoted.indexOf('`') < 0) {
184                return quoted;
185            }
186    
187            if (quoted.startsWith("$")) {
188                return "$" + unquoteIdentifier(quoted.substring(1));
189            }
190            else {
191                return unquoteIdentifier(quoted);
192            }
193        }
194    
195        /** @return <code>null</code> iff the tye has syntactic errors */
196        @Nullable
197        public static FqName toQualifiedName(@NotNull JetUserType userType) {
198            List<String> reversedNames = Lists.newArrayList();
199    
200            JetUserType current = userType;
201            while (current != null) {
202                String name = current.getReferencedName();
203                if (name == null) return null;
204    
205                reversedNames.add(name);
206                current = current.getQualifier();
207            }
208    
209            return FqName.fromSegments(ContainerUtil.reverse(reversedNames));
210        }
211    
212        @Nullable
213        public static Name getShortName(@NotNull JetAnnotationEntry annotation) {
214            JetTypeReference typeReference = annotation.getTypeReference();
215            assert typeReference != null : "Annotation entry hasn't typeReference " + annotation.getText();
216            JetTypeElement typeElement = typeReference.getTypeElement();
217            if (typeElement instanceof JetUserType) {
218                JetUserType userType = (JetUserType) typeElement;
219                String shortName = userType.getReferencedName();
220                if (shortName != null) {
221                    return Name.identifier(shortName);
222                }
223            }
224            return null;
225        }
226    
227        public static boolean isDeprecated(@NotNull JetModifierListOwner owner) {
228            JetModifierList modifierList = owner.getModifierList();
229            if (modifierList != null) {
230                List<JetAnnotationEntry> annotationEntries = modifierList.getAnnotationEntries();
231                for (JetAnnotationEntry annotation : annotationEntries) {
232                    Name shortName = getShortName(annotation);
233                    if (KotlinBuiltIns.getInstance().getDeprecatedAnnotation().getName().equals(shortName)) {
234                        return true;
235                    }
236                }
237            }
238            return false;
239        }
240    
241        @Nullable
242        @IfNotParsed
243        public static ImportPath getImportPath(@NotNull JetImportDirective importDirective) {
244            if (PsiTreeUtil.hasErrorElements(importDirective)) {
245                return null;
246            }
247    
248            FqName importFqn = getFQName(importDirective.getImportedReference());
249            if (importFqn == null) {
250                return null;
251            }
252    
253            Name alias = null;
254            String aliasName = importDirective.getAliasName();
255            if (aliasName != null) {
256                alias = Name.identifier(aliasName);
257            }
258    
259            return new ImportPath(importFqn, importDirective.isAllUnder(), alias);
260        }
261    
262        @Nullable
263        public static <T extends PsiElement> T getDirectParentOfTypeForBlock(@NotNull JetBlockExpression block, @NotNull Class<T> aClass) {
264            T parent = PsiTreeUtil.getParentOfType(block, aClass);
265            if (parent instanceof JetIfExpression) {
266                JetIfExpression ifExpression = (JetIfExpression) parent;
267                if (ifExpression.getElse() == block || ifExpression.getThen() == block) {
268                    return parent;
269                }
270            }
271            if (parent instanceof JetWhenExpression) {
272                JetWhenExpression whenExpression = (JetWhenExpression) parent;
273                for (JetWhenEntry whenEntry : whenExpression.getEntries()) {
274                    if (whenEntry.getExpression() == block) {
275                        return parent;
276                    }
277                }
278            }
279            if (parent instanceof JetFunctionLiteral) {
280                JetFunctionLiteral functionLiteral = (JetFunctionLiteral) parent;
281                if (functionLiteral.getBodyExpression() == block) {
282                    return parent;
283                }
284            }
285            if (parent instanceof JetTryExpression) {
286                JetTryExpression tryExpression = (JetTryExpression) parent;
287                if (tryExpression.getTryBlock() == block) {
288                    return parent;
289                }
290                for (JetCatchClause clause : tryExpression.getCatchClauses()) {
291                    if (clause.getCatchBody() == block) {
292                        return parent;
293                    }
294                }
295            }
296            return null;
297        }
298    
299        public static boolean isImplicitlyUsed(@NotNull JetElement element) {
300            PsiElement parent = element.getParent();
301            if (!(parent instanceof JetBlockExpression)) return true;
302            JetBlockExpression block = (JetBlockExpression) parent;
303            List<JetElement> statements = block.getStatements();
304            if (statements.get(statements.size() - 1) == element) {
305                JetExpression expression = getDirectParentOfTypeForBlock(block, JetIfExpression.class);
306                if (expression == null) {
307                    expression = getDirectParentOfTypeForBlock(block, JetWhenExpression.class);
308                }
309                if (expression == null) {
310                    expression = getDirectParentOfTypeForBlock(block, JetFunctionLiteral.class);
311                }
312                if (expression == null) {
313                    expression = getDirectParentOfTypeForBlock(block, JetTryExpression.class);
314                }
315                if (expression != null) {
316                    return isImplicitlyUsed(expression);
317                }
318            }
319            return false;
320        }
321    
322        public static void deleteClass(@NotNull JetClassOrObject clazz) {
323            CheckUtil.checkWritable(clazz);
324            JetFile file = clazz.getContainingJetFile();
325            if (isLocal(clazz) || file.getDeclarations().size() > 1) {
326                PsiElement parent = clazz.getParent();
327                CodeEditUtil.removeChild(parent.getNode(), clazz.getNode());
328            }
329            else {
330                file.delete();
331            }
332        }
333    
334        @Nullable
335        public static Name getAliasName(@NotNull JetImportDirective importDirective) {
336            String aliasName = importDirective.getAliasName();
337            JetExpression importedReference = importDirective.getImportedReference();
338            if (importedReference == null) {
339                return null;
340            }
341            JetSimpleNameExpression referenceExpression = getLastReference(importedReference);
342            if (aliasName == null) {
343                aliasName = referenceExpression != null ? referenceExpression.getReferencedName() : null;
344            }
345    
346            return aliasName != null && !aliasName.isEmpty() ? Name.identifier(aliasName) : null;
347        }
348    
349        @Nullable
350        public static JetSimpleNameExpression getLastReference(@NotNull JetExpression importedReference) {
351            JetElement selector = PsiUtilPackage.getQualifiedElementSelector(importedReference);
352            return selector instanceof JetSimpleNameExpression ? (JetSimpleNameExpression) selector : null;
353        }
354    
355        public static boolean isSelectorInQualified(@NotNull JetSimpleNameExpression nameExpression) {
356            JetElement qualifiedElement = PsiUtilPackage.getQualifiedElement(nameExpression);
357            return qualifiedElement instanceof JetQualifiedExpression
358                   || ((qualifiedElement instanceof JetUserType) && ((JetUserType) qualifiedElement).getQualifier() != null);
359        }
360    
361        public static boolean isLHSOfDot(@NotNull JetExpression expression) {
362            PsiElement parent = expression.getParent();
363            if (!(parent instanceof JetQualifiedExpression)) return false;
364            JetQualifiedExpression qualifiedParent = (JetQualifiedExpression) parent;
365            return qualifiedParent.getReceiverExpression() == expression || isLHSOfDot(qualifiedParent);
366        }
367    
368        public static boolean isVoidType(@Nullable JetTypeReference typeReference) {
369            if (typeReference == null) {
370                return false;
371            }
372    
373            return KotlinBuiltIns.getInstance().getUnit().getName().asString().equals(typeReference.getText());
374        }
375    
376        public static boolean isSafeCall(@NotNull Call call) {
377            ASTNode callOperationNode = call.getCallOperationNode();
378            return callOperationNode != null && callOperationNode.getElementType() == JetTokens.SAFE_ACCESS;
379        }
380    
381        // SCRIPT: is declaration in script?
382        public static boolean isScriptDeclaration(@NotNull JetDeclaration namedDeclaration) {
383            return getScript(namedDeclaration) != null;
384        }
385    
386        // SCRIPT: get script from top-level declaration
387        @Nullable
388        public static JetScript getScript(@NotNull JetDeclaration namedDeclaration) {
389            PsiElement parent = namedDeclaration.getParent();
390            if (parent != null && parent.getParent() instanceof JetScript) {
391                return (JetScript) parent.getParent();
392            }
393            else {
394                return null;
395            }
396        }
397    
398        public static boolean isVariableNotParameterDeclaration(@NotNull JetDeclaration declaration) {
399            if (!(declaration instanceof JetVariableDeclaration)) return false;
400            if (declaration instanceof JetProperty) return true;
401            assert declaration instanceof JetMultiDeclarationEntry;
402            JetMultiDeclarationEntry multiDeclarationEntry = (JetMultiDeclarationEntry) declaration;
403            return !(multiDeclarationEntry.getParent().getParent() instanceof JetForExpression);
404        }
405    
406        @Nullable
407        public static Name getConventionName(@NotNull JetSimpleNameExpression simpleNameExpression) {
408            if (simpleNameExpression.getIdentifier() != null) {
409                return simpleNameExpression.getReferencedNameAsName();
410            }
411    
412            PsiElement firstChild = simpleNameExpression.getFirstChild();
413            if (firstChild != null) {
414                IElementType elementType = firstChild.getNode().getElementType();
415                if (elementType instanceof JetToken) {
416                    JetToken jetToken = (JetToken) elementType;
417                    return OperatorConventions.getNameForOperationSymbol(jetToken);
418                }
419            }
420    
421            return null;
422        }
423    
424        @Nullable
425        public static PsiElement getTopmostParentOfTypes(@Nullable PsiElement element, @NotNull Class<? extends PsiElement>... parentTypes) {
426            if (element == null) {
427                return null;
428            }
429    
430            PsiElement result = null;
431            PsiElement parent = element.getParent();
432            while (parent != null) {
433                if (PsiTreeUtil.instanceOf(parent, parentTypes)) {
434                    result = parent;
435                }
436    
437                parent = parent.getParent();
438            }
439    
440            return result;
441        }
442    
443        public static boolean isNullConstant(@NotNull JetExpression expression) {
444            JetExpression deparenthesized = deparenthesize(expression);
445            return deparenthesized instanceof JetConstantExpression && deparenthesized.getNode().getElementType() == JetNodeTypes.NULL;
446        }
447    
448        public static boolean isAbstract(@NotNull JetDeclarationWithBody declaration) {
449            return declaration.getBodyExpression() == null;
450        }
451    
452        public static boolean isBackingFieldReference(@NotNull JetSimpleNameExpression expression) {
453            return expression.getReferencedNameElementType() == JetTokens.FIELD_IDENTIFIER;
454        }
455    
456        public static boolean isBackingFieldReference(@Nullable JetElement element) {
457            return element instanceof JetSimpleNameExpression && isBackingFieldReference((JetSimpleNameExpression)element);
458        }
459    
460        @Nullable
461        private static FqName getFQName(@Nullable JetExpression expression) {
462            if (expression == null) {
463                return null;
464            }
465    
466            if (expression instanceof JetDotQualifiedExpression) {
467                JetDotQualifiedExpression dotQualifiedExpression = (JetDotQualifiedExpression) expression;
468                FqName parentFqn = getFQName(dotQualifiedExpression.getReceiverExpression());
469                Name child = getName(dotQualifiedExpression.getSelectorExpression());
470    
471                return parentFqn != null && child != null ? parentFqn.child(child) : null;
472            }
473            else if (expression instanceof JetSimpleNameExpression) {
474                JetSimpleNameExpression simpleNameExpression = (JetSimpleNameExpression) expression;
475                return FqName.topLevel(simpleNameExpression.getReferencedNameAsName());
476            }
477            else {
478                throw new IllegalArgumentException("Can't construct fqn for: " + expression.getClass().toString());
479            }
480        }
481    
482        @Nullable
483        private static Name getName(@Nullable JetExpression expression) {
484            if (expression == null) {
485                return null;
486            }
487    
488            if (expression instanceof JetSimpleNameExpression) {
489                return ((JetSimpleNameExpression) expression).getReferencedNameAsName();
490            }
491            else {
492                throw new IllegalArgumentException("Can't construct name for: " + expression.getClass().toString());
493            }
494        }
495    
496        @Nullable
497        public static JetElement getLastStatementInABlock(@Nullable JetBlockExpression blockExpression) {
498            if (blockExpression == null) return null;
499            List<JetElement> statements = blockExpression.getStatements();
500            return statements.isEmpty() ? null : statements.get(statements.size() - 1);
501        }
502    
503        public static boolean isTrait(@NotNull JetClassOrObject classOrObject) {
504            return classOrObject instanceof JetClass && ((JetClass) classOrObject).isTrait();
505        }
506    
507        @Nullable
508        public static JetClassOrObject getOutermostClassOrObject(@NotNull JetClassOrObject classOrObject) {
509            JetClassOrObject current = classOrObject;
510            while (true) {
511                PsiElement parent = current.getParent();
512                assert classOrObject.getParent() != null : "Class with no parent: " + classOrObject.getText();
513    
514                if (parent instanceof PsiFile) {
515                    return current;
516                }
517                if (parent instanceof JetClassObject) {
518                    // current class IS the class object declaration
519                    parent = parent.getParent();
520                    assert parent instanceof JetClassBody : "Parent of class object is not a class body: " + parent;
521                }
522                if (!(parent instanceof JetClassBody)) {
523                    // It is a local class, no legitimate outer
524                    return current;
525                }
526    
527                current = (JetClassOrObject) parent.getParent();
528            }
529        }
530    
531        @Nullable
532        public static JetClass getClassIfParameterIsProperty(@NotNull JetParameter jetParameter) {
533            if (jetParameter.getValOrVarNode() != null) {
534                PsiElement parent = jetParameter.getParent();
535                if (parent instanceof JetParameterList && parent.getParent() instanceof JetClass) {
536                    return (JetClass) parent.getParent();
537                }
538            }
539    
540            return null;
541        }
542    
543        @Nullable
544        private static IElementType getOperation(@NotNull JetExpression expression) {
545            if (expression instanceof JetQualifiedExpression) {
546                return ((JetQualifiedExpression) expression).getOperationSign();
547            }
548            else if (expression instanceof JetOperationExpression) {
549                return ((JetOperationExpression) expression).getOperationReference().getReferencedNameElementType();
550            }
551            return null;
552        }
553    
554    
555        private static int getPriority(@NotNull JetExpression expression) {
556            int maxPriority = JetExpressionParsing.Precedence.values().length + 1;
557    
558            // same as postfix operations
559            if (expression instanceof JetPostfixExpression ||
560                expression instanceof JetQualifiedExpression ||
561                expression instanceof JetCallExpression ||
562                expression instanceof JetArrayAccessExpression) {
563                return maxPriority - 1;
564            }
565    
566            if (expression instanceof JetPrefixExpression) return maxPriority - 2;
567    
568            if (expression instanceof JetDeclaration || expression instanceof JetStatementExpression || expression instanceof JetIfExpression) {
569                return 0;
570            }
571    
572            IElementType operation = getOperation(expression);
573            for (JetExpressionParsing.Precedence precedence : JetExpressionParsing.Precedence.values()) {
574                if (precedence != JetExpressionParsing.Precedence.PREFIX && precedence != JetExpressionParsing.Precedence.POSTFIX &&
575                    precedence.getOperations().contains(operation)) {
576                    return maxPriority - precedence.ordinal() - 1;
577                }
578            }
579    
580            return maxPriority;
581        }
582    
583        public static boolean areParenthesesUseless(@NotNull JetParenthesizedExpression expression) {
584            JetExpression innerExpression = expression.getExpression();
585            if (innerExpression == null) return true;
586    
587            PsiElement parent = expression.getParent();
588            if (!(parent instanceof JetExpression)) return true;
589    
590            return !areParenthesesNecessary(innerExpression, expression, (JetExpression) parent);
591        }
592    
593        public static boolean areParenthesesNecessary(@NotNull JetExpression innerExpression, @NotNull JetExpression currentInner, @NotNull JetExpression parentExpression) {
594            if (parentExpression instanceof JetParenthesizedExpression || innerExpression instanceof JetParenthesizedExpression) {
595                return false;
596            }
597    
598            if (parentExpression instanceof JetPackageDirective) return false;
599    
600            if (parentExpression instanceof JetWhenExpression || innerExpression instanceof JetWhenExpression) {
601                return false;
602            }
603    
604            if (innerExpression instanceof JetIfExpression) {
605                PsiElement current = parentExpression;
606    
607                while (!(current instanceof JetBlockExpression || current instanceof JetDeclaration || current instanceof JetStatementExpression)) {
608                    if (current.getTextRange().getEndOffset() != currentInner.getTextRange().getEndOffset()) {
609                        return current.getText().charAt(current.getTextLength() - 1) != ')'; // if current expression is "guarded" by parenthesis, no extra parenthesis is necessary
610                    }
611    
612                    current = current.getParent();
613                }
614            }
615    
616            IElementType innerOperation = getOperation(innerExpression);
617            IElementType parentOperation = getOperation(parentExpression);
618    
619            // 'return (@label{...})' case
620            if (parentExpression instanceof JetReturnExpression && innerOperation == JetTokens.LABEL_IDENTIFIER) {
621                return true;
622            }
623    
624            // '(x: Int) < y' case
625            if (innerExpression instanceof JetBinaryExpressionWithTypeRHS && parentOperation == JetTokens.LT) {
626                return true;
627            }
628    
629            int innerPriority = getPriority(innerExpression);
630            int parentPriority = getPriority(parentExpression);
631    
632            if (innerPriority == parentPriority) {
633                if (parentExpression instanceof JetBinaryExpression) {
634                    if (innerOperation == JetTokens.ANDAND || innerOperation == JetTokens.OROR) {
635                        return false;
636                    }
637                    return ((JetBinaryExpression) parentExpression).getRight() == currentInner;
638                }
639    
640                //'-(-x)' case
641                if (parentExpression instanceof JetPrefixExpression && innerExpression instanceof JetPrefixExpression) {
642                    return innerOperation == parentOperation && (innerOperation == JetTokens.PLUS || innerOperation == JetTokens.MINUS);
643                }
644                return false;
645            }
646    
647            return innerPriority < parentPriority;
648        }
649    
650        public static boolean isAssignment(@NotNull PsiElement element) {
651            return element instanceof JetBinaryExpression &&
652                   JetTokens.ALL_ASSIGNMENTS.contains(((JetBinaryExpression) element).getOperationToken());
653        }
654    
655        public static boolean isOrdinaryAssignment(@NotNull PsiElement element) {
656            return element instanceof JetBinaryExpression &&
657                   ((JetBinaryExpression) element).getOperationToken().equals(JetTokens.EQ);
658        }
659    
660        @Nullable
661        public static JetElement getOutermostLastBlockElement(@Nullable JetElement element, @NotNull Predicate<JetElement> checkElement) {
662            if (element == null) return null;
663    
664            if (!(element instanceof JetBlockExpression)) return checkElement.apply(element) ? element : null;
665    
666            JetBlockExpression block = (JetBlockExpression)element;
667            int n = block.getStatements().size();
668    
669            if (n == 0) return null;
670    
671            JetElement lastElement = block.getStatements().get(n - 1);
672            return checkElement.apply(lastElement) ? lastElement : null;
673        }
674    
675        public static boolean checkVariableDeclarationInBlock(@NotNull JetBlockExpression block, @NotNull String varName) {
676            for (JetElement element : block.getStatements()) {
677                if (element instanceof JetVariableDeclaration) {
678                    if (((JetVariableDeclaration) element).getNameAsSafeName().asString().equals(varName)) {
679                        return true;
680                    }
681                }
682            }
683    
684            return false;
685        }
686    
687        public static boolean checkWhenExpressionHasSingleElse(@NotNull JetWhenExpression whenExpression) {
688            int elseCount = 0;
689            for (JetWhenEntry entry : whenExpression.getEntries()) {
690                if (entry.isElse()) {
691                    elseCount++;
692                }
693            }
694            return (elseCount == 1);
695        }
696    
697        @Nullable
698        public static PsiElement skipTrailingWhitespacesAndComments(@Nullable PsiElement element)  {
699            return PsiTreeUtil.skipSiblingsForward(element, PsiWhiteSpace.class, PsiComment.class);
700        }
701    
702        public static final Predicate<JetElement> ANY_JET_ELEMENT = new Predicate<JetElement>() {
703            @Override
704            public boolean apply(@Nullable JetElement input) {
705                return true;
706            }
707        };
708    
709        @NotNull
710        public static String getText(@Nullable PsiElement element) {
711            return element != null ? element.getText() : "";
712        }
713    
714        @Nullable
715        public static String getNullableText(@Nullable PsiElement element) {
716            return element != null ? element.getText() : null;
717        }
718    
719        /**
720         * CommentUtilCore.isComment fails if element <strong>inside</strong> comment.
721         *
722         * Also, we can not add KDocTokens to COMMENTS TokenSet, because it is used in JetParserDefinition.getCommentTokens(),
723         * and therefor all COMMENTS tokens will be ignored by PsiBuilder.
724         *
725         * @param element
726         * @return
727         */
728        public static boolean isInComment(PsiElement element) {
729            return CommentUtilCore.isComment(element) || element instanceof KDocElement;
730        }
731    
732        @Nullable
733        public static PsiElement getOutermostParent(@NotNull PsiElement element, @NotNull PsiElement upperBound, boolean strict) {
734            PsiElement parent = strict ? element.getParent() : element;
735            while (parent != null && parent.getParent() != upperBound) {
736                parent = parent.getParent();
737            }
738    
739            return parent;
740        }
741    
742        public static <T extends PsiElement> T getLastChildByType(@NotNull PsiElement root, @NotNull Class<? extends T>... elementTypes) {
743            PsiElement[] children = root.getChildren();
744    
745            for (int i = children.length - 1; i >= 0; i--) {
746                if (PsiTreeUtil.instanceOf(children[i], elementTypes)) {
747                    //noinspection unchecked
748                    return (T) children[i];
749                }
750            }
751    
752            return null;
753        }
754    
755        @Nullable
756        public static JetElement getOutermostDescendantElement(
757                @Nullable PsiElement root,
758                boolean first,
759                final @NotNull Predicate<JetElement> predicate
760        ) {
761            if (!(root instanceof JetElement)) return null;
762    
763            final List<JetElement> results = Lists.newArrayList();
764    
765            root.accept(
766                    new JetVisitorVoid() {
767                        @Override
768                        public void visitJetElement(@NotNull JetElement element) {
769                            if (predicate.apply(element)) {
770                                //noinspection unchecked
771                                results.add(element);
772                            }
773                            else {
774                                element.acceptChildren(this);
775                            }
776                        }
777                    }
778            );
779    
780            if (results.isEmpty()) return null;
781    
782            return first ? results.get(0) : results.get(results.size() - 1);
783        }
784    
785        @Nullable
786        public static PsiElement findChildByType(@NotNull PsiElement element, @NotNull IElementType type) {
787            ASTNode node = element.getNode().findChildByType(type);
788            return node == null ? null : node.getPsi();
789        }
790    
791        @Nullable
792        public static JetExpression getCalleeExpressionIfAny(@NotNull JetExpression expression) {
793            if (expression instanceof JetSimpleNameExpression) {
794                return expression;
795            }
796            if (expression instanceof JetCallElement) {
797                JetCallElement callExpression = (JetCallElement) expression;
798                return callExpression.getCalleeExpression();
799            }
800            if (expression instanceof JetQualifiedExpression) {
801                JetExpression selectorExpression = ((JetQualifiedExpression) expression).getSelectorExpression();
802                if (selectorExpression != null) {
803                    return getCalleeExpressionIfAny(selectorExpression);
804                }
805            }
806            if (expression instanceof JetUnaryExpression) {
807                return ((JetUnaryExpression) expression).getOperationReference();
808            }
809            if (expression instanceof JetBinaryExpression) {
810                return ((JetBinaryExpression) expression).getOperationReference();
811            }
812            return null;
813        }
814    
815        @Nullable
816        public static PsiElement skipSiblingsBackwardByPredicate(@Nullable PsiElement element, Predicate<PsiElement> elementsToSkip) {
817            if (element == null) return null;
818            for (PsiElement e = element.getPrevSibling(); e != null; e = e.getPrevSibling()) {
819                if (elementsToSkip.apply(e)) continue;
820                return e;
821            }
822            return null;
823        }
824    
825        @Nullable
826        public static PsiElement skipSiblingsForwardByPredicate(@Nullable PsiElement element, Predicate<PsiElement> elementsToSkip) {
827            if (element == null) return null;
828            for (PsiElement e = element.getNextSibling(); e != null; e = e.getNextSibling()) {
829                if (elementsToSkip.apply(e)) continue;
830                return e;
831            }
832            return null;
833        }
834    
835        // Delete given element and all the elements separating it from the neighboring elements of the same class
836        public static void deleteElementWithDelimiters(@NotNull PsiElement element) {
837            PsiElement paramBefore = PsiTreeUtil.getPrevSiblingOfType(element, element.getClass());
838    
839            PsiElement from;
840            PsiElement to;
841            if (paramBefore != null) {
842                from = paramBefore.getNextSibling();
843                to = element;
844            }
845            else {
846                PsiElement paramAfter = PsiTreeUtil.getNextSiblingOfType(element, element.getClass());
847    
848                from = element;
849                to = paramAfter != null ? paramAfter.getPrevSibling() : element;
850            }
851    
852            PsiElement parent = element.getParent();
853    
854            parent.deleteChildRange(from, to);
855        }
856    
857        // Delete element if it doesn't contain children of a given type
858        public static <T extends PsiElement> void deleteChildlessElement(PsiElement element, Class<T> childClass) {
859            if (PsiTreeUtil.getChildrenOfType(element, childClass) == null) {
860                element.delete();
861            }
862        }
863    
864        public static PsiElement ascendIfPropertyAccessor(PsiElement element) {
865            if (element instanceof JetPropertyAccessor) {
866                return element.getParent();
867            }
868            return element;
869        }
870    
871        @NotNull
872        public static String getElementTextWithContext(@NotNull JetElement element) {
873            if (element instanceof JetFile) {
874                return element.getContainingFile().getText();
875            }
876    
877            // Find parent for element among file children
878            PsiElement inFileParent = PsiTreeUtil.findFirstParent(element, new Condition<PsiElement>() {
879                @Override
880                public boolean value(PsiElement parentCandidate) {
881                    return parentCandidate != null && parentCandidate.getParent() instanceof JetFile;
882                }
883            });
884    
885            assert inFileParent != null : "For non-file element we should always be able to find parent in file children";
886    
887            int startContextOffset = inFileParent.getTextRange().getStartOffset();
888            int elementContextOffset = element.getTextRange().getStartOffset();
889    
890            int inFileParentOffset = elementContextOffset - startContextOffset;
891    
892            return new StringBuilder(inFileParent.getText()).insert(inFileParentOffset, "<caret>").toString();
893        }
894    
895        @Nullable
896        public static JetModifierList replaceModifierList(@NotNull JetModifierListOwner owner, @Nullable JetModifierList modifierList) {
897            JetModifierList oldModifierList = owner.getModifierList();
898            if (modifierList == null) {
899                if (oldModifierList != null) oldModifierList.delete();
900                return null;
901            }
902            else {
903                if (oldModifierList == null) {
904                    PsiElement firstChild = owner.getFirstChild();
905                    return (JetModifierList) owner.addBefore(modifierList, firstChild);
906                }
907                else {
908                    return (JetModifierList) oldModifierList.replace(modifierList);
909                }
910            }
911        }
912    
913        @Nullable
914        public static String getPackageName(@NotNull JetElement element) {
915            JetFile file = element.getContainingJetFile();
916            JetPackageDirective header = PsiTreeUtil.findChildOfType(file, JetPackageDirective.class);
917    
918            return header != null ? header.getQualifiedName() : null;
919        }
920    
921        @Nullable
922        public static JetElement getEnclosingElementForLocalDeclaration(@Nullable JetDeclaration declaration) {
923            if (declaration instanceof JetTypeParameter) {
924                declaration = PsiTreeUtil.getParentOfType(declaration, JetNamedDeclaration.class);
925            }
926            else if (declaration instanceof JetParameter) {
927                PsiElement parent = declaration.getParent();
928                if (parent != null && parent.getParent() instanceof JetNamedFunction) {
929                    declaration = (JetNamedFunction) parent.getParent();
930                }
931            }
932    
933            //noinspection unchecked
934            JetElement container = PsiTreeUtil.getParentOfType(
935                    declaration,
936                    JetBlockExpression.class, JetClassInitializer.class, JetProperty.class, JetFunction.class, JetParameter.class
937            );
938            if (container == null) return null;
939    
940            return (container instanceof JetClassInitializer) ? ((JetClassInitializer) container).getBody() : container;
941        }
942    
943        public static boolean isLocal(@NotNull JetDeclaration declaration) {
944            return getEnclosingElementForLocalDeclaration(declaration) != null;
945        }
946    
947        @Nullable
948        public static JetToken getOperationToken(@NotNull JetOperationExpression expression) {
949            JetSimpleNameExpression operationExpression = expression.getOperationReference();
950            IElementType elementType = operationExpression.getReferencedNameElementType();
951            assert elementType == null || elementType instanceof JetToken :
952                    "JetOperationExpression should have operation token of type JetToken: " +
953                    expression;
954            return (JetToken) elementType;
955        }
956    
957        public static boolean isLabelIdentifierExpression(PsiElement element) {
958            return element instanceof JetSimpleNameExpression &&
959                   ((JetSimpleNameExpression) element).getReferencedNameElementType() == JetTokens.LABEL_IDENTIFIER;
960        }
961    }