java: receiver parameter highlighting

This commit is contained in:
Roman Shevchenko
2015-07-02 15:29:22 +03:00
parent e737db6dcc
commit fd0061d246
10 changed files with 182 additions and 81 deletions

View File

@@ -36,6 +36,7 @@ import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.ObjectUtils;
import com.intellij.util.containers.HashSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -663,8 +664,7 @@ public class AnnotationsHighlightUtil {
if (methods.length == 1) {
PsiType expected = new PsiImmediateClassType((PsiClass)target, PsiSubstitutor.EMPTY).createArrayType();
if (!expected.equals(methods[0].getReturnType())) {
return JavaErrorMessages.message("annotation.container.bad.type", container.getQualifiedName(), JavaHighlightUtil
.formatType(expected));
return JavaErrorMessages.message("annotation.container.bad.type", container.getQualifiedName(), JavaHighlightUtil.formatType(expected));
}
}
@@ -698,6 +698,64 @@ public class AnnotationsHighlightUtil {
return container;
}
@Nullable
static HighlightInfo checkReceiverPlacement(PsiReceiverParameter parameter) {
PsiElement owner = parameter.getParent().getParent();
if (owner == null) return null;
if (!(owner instanceof PsiMethod)) {
String text = JavaErrorMessages.message("receiver.wrong.context");
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(parameter.getIdentifier()).descriptionAndTooltip(text).create();
}
PsiMethod method = (PsiMethod)owner;
if (isStatic(method) || (method).isConstructor() && isStatic(method.getContainingClass())) {
String text = JavaErrorMessages.message("receiver.static.context");
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(parameter.getIdentifier()).descriptionAndTooltip(text).create();
}
PsiElement leftNeighbour = PsiTreeUtil.skipSiblingsBackward(parameter, PsiWhiteSpace.class);
if (leftNeighbour != null && !PsiUtil.isJavaToken(leftNeighbour, JavaTokenType.LPARENTH)) {
String text = JavaErrorMessages.message("receiver.wrong.position");
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(parameter.getIdentifier()).descriptionAndTooltip(text).create();
}
return null;
}
@Nullable
static HighlightInfo checkReceiverType(PsiReceiverParameter parameter) {
PsiElement owner = parameter.getParent().getParent();
if (!(owner instanceof PsiMethod)) return null;
PsiMethod method = (PsiMethod)owner;
PsiClass enclosingClass = method.getContainingClass();
if (method.isConstructor() && enclosingClass != null) {
enclosingClass = enclosingClass.getContainingClass();
}
if (enclosingClass != null && !enclosingClass.equals(PsiUtil.resolveClassInType(parameter.getType()))) {
PsiElement range = ObjectUtils.notNull(parameter.getTypeElement(), parameter);
String text = JavaErrorMessages.message("receiver.type.mismatch");
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(range).descriptionAndTooltip(text).create();
}
PsiThisExpression identifier = parameter.getIdentifier();
if (enclosingClass != null && !enclosingClass.equals(PsiUtil.resolveClassInType(identifier.getType()))) {
String text = JavaErrorMessages.message("receiver.name.mismatch");
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(identifier).descriptionAndTooltip(text).create();
}
return null;
}
private static boolean isStatic(PsiModifierListOwner owner) {
if (owner == null) return false;
if (owner instanceof PsiClass && ((PsiClass)owner).getContainingClass() == null) return true;
PsiModifierList modifierList = owner.getModifierList();
return modifierList != null && modifierList.hasModifierProperty(PsiModifier.STATIC);
}
@Nullable
public static RetentionPolicy getRetentionPolicy(@NotNull PsiClass annotation) {
PsiModifierList modifierList = annotation.getModifierList();

View File

@@ -965,6 +965,9 @@ public class HighlightUtil extends HighlightUtilBase {
else if (modifierOwner instanceof PsiLocalVariable || modifierOwner instanceof PsiParameter) {
isAllowed = PsiModifier.FINAL.equals(modifier);
}
else if (modifierOwner instanceof PsiReceiverParameter) {
isAllowed = false;
}
isAllowed &= incompatibles != null;
if (!isAllowed) {
@@ -2899,7 +2902,8 @@ public class HighlightUtil extends HighlightUtilBase {
EXTENSION_METHODS(LanguageLevel.JDK_1_8, "feature.extension.methods"),
METHOD_REFERENCES(LanguageLevel.JDK_1_8, "feature.method.references"),
LAMBDA_EXPRESSIONS(LanguageLevel.JDK_1_8, "feature.lambda.expressions"),
TYPE_ANNOTATIONS(LanguageLevel.JDK_1_8, "feature.type.annotations");
TYPE_ANNOTATIONS(LanguageLevel.JDK_1_8, "feature.type.annotations"),
RECEIVERS(LanguageLevel.JDK_1_8, "feature.type.receivers");
private final LanguageLevel level;
private final String key;

View File

@@ -1460,12 +1460,10 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
@Override
public void visitThisExpression(PsiThisExpression expr) {
myHolder.add(HighlightUtil.checkThisOrSuperExpressionInIllegalContext(expr, expr.getQualifier(), myLanguageLevel));
if (!myHolder.hasErrorResults()) {
myHolder.add(HighlightUtil.checkMemberReferencedBeforeConstructorCalled(expr, null, myFile));
}
if (!myHolder.hasErrorResults()) {
visitExpression(expr);
if (!(expr.getParent() instanceof PsiReceiverParameter)) {
myHolder.add(HighlightUtil.checkThisOrSuperExpressionInIllegalContext(expr, expr.getQualifier(), myLanguageLevel));
if (!myHolder.hasErrorResults()) myHolder.add(HighlightUtil.checkMemberReferencedBeforeConstructorCalled(expr, null, myFile));
if (!myHolder.hasErrorResults()) visitExpression(expr);
}
}
@@ -1580,6 +1578,14 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
}
}
@Override
public void visitReceiverParameter(PsiReceiverParameter parameter) {
super.visitReceiverParameter(parameter);
if (!myHolder.hasErrorResults()) myHolder.add(checkFeature(parameter, Feature.RECEIVERS));
if (!myHolder.hasErrorResults()) myHolder.add(AnnotationsHighlightUtil.checkReceiverPlacement(parameter));
if (!myHolder.hasErrorResults()) myHolder.add(AnnotationsHighlightUtil.checkReceiverType(parameter));
}
@Nullable
private HighlightInfo checkFeature(@NotNull PsiElement element, @NotNull Feature feature) {
return HighlightUtil.checkFeature(element, feature, myLanguageLevel, myFile);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2000-2014 JetBrains s.r.o.
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@ package com.intellij.codeInsight;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.psi.*;
import com.intellij.psi.PsiAnnotation.TargetType;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -28,28 +29,27 @@ import java.util.Set;
* @author peter
*/
public class AnnotationTargetUtil {
public static final Set<PsiAnnotation.TargetType> DEFAULT_TARGETS = Collections.unmodifiableSet(ContainerUtil.newHashSet(
PsiAnnotation.TargetType.PACKAGE, PsiAnnotation.TargetType.TYPE, PsiAnnotation.TargetType.ANNOTATION_TYPE,
PsiAnnotation.TargetType.FIELD, PsiAnnotation.TargetType.METHOD, PsiAnnotation.TargetType.CONSTRUCTOR,
PsiAnnotation.TargetType.PARAMETER, PsiAnnotation.TargetType.LOCAL_VARIABLE));
private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.AnnotationUtil");
private static final PsiAnnotation.TargetType[] PACKAGE_TARGETS = {PsiAnnotation.TargetType.PACKAGE};
private static final PsiAnnotation.TargetType[] TYPE_USE_TARGETS = {PsiAnnotation.TargetType.TYPE_USE};
private static final PsiAnnotation.TargetType[] ANNOTATION_TARGETS = {PsiAnnotation.TargetType.ANNOTATION_TYPE, PsiAnnotation.TargetType.TYPE, PsiAnnotation.TargetType.TYPE_USE};
private static final PsiAnnotation.TargetType[] TYPE_TARGETS = {PsiAnnotation.TargetType.TYPE, PsiAnnotation.TargetType.TYPE_USE};
private static final PsiAnnotation.TargetType[] TYPE_PARAMETER_TARGETS = {
PsiAnnotation.TargetType.TYPE_PARAMETER, PsiAnnotation.TargetType.TYPE_USE};
private static final PsiAnnotation.TargetType[] CONSTRUCTOR_TARGETS = {PsiAnnotation.TargetType.CONSTRUCTOR, PsiAnnotation.TargetType.TYPE_USE};
private static final PsiAnnotation.TargetType[] METHOD_TARGETS = {PsiAnnotation.TargetType.METHOD, PsiAnnotation.TargetType.TYPE_USE};
private static final PsiAnnotation.TargetType[] FIELD_TARGETS = {PsiAnnotation.TargetType.FIELD, PsiAnnotation.TargetType.TYPE_USE};
private static final PsiAnnotation.TargetType[] PARAMETER_TARGETS = {PsiAnnotation.TargetType.PARAMETER, PsiAnnotation.TargetType.TYPE_USE};
private static final PsiAnnotation.TargetType[] LOCAL_VARIABLE_TARGETS ={
PsiAnnotation.TargetType.LOCAL_VARIABLE, PsiAnnotation.TargetType.TYPE_USE};
public static final Set<TargetType> DEFAULT_TARGETS = ContainerUtil.immutableSet(
TargetType.PACKAGE, TargetType.TYPE, TargetType.ANNOTATION_TYPE, TargetType.FIELD, TargetType.METHOD, TargetType.CONSTRUCTOR,
TargetType.PARAMETER, TargetType.LOCAL_VARIABLE);
private static final TargetType[] PACKAGE_TARGETS = {TargetType.PACKAGE};
private static final TargetType[] TYPE_USE_TARGETS = {TargetType.TYPE_USE};
private static final TargetType[] ANNOTATION_TARGETS = {TargetType.ANNOTATION_TYPE, TargetType.TYPE, TargetType.TYPE_USE};
private static final TargetType[] TYPE_TARGETS = {TargetType.TYPE, TargetType.TYPE_USE};
private static final TargetType[] TYPE_PARAMETER_TARGETS = {TargetType.TYPE_PARAMETER, TargetType.TYPE_USE};
private static final TargetType[] CONSTRUCTOR_TARGETS = {TargetType.CONSTRUCTOR, TargetType.TYPE_USE};
private static final TargetType[] METHOD_TARGETS = {TargetType.METHOD, TargetType.TYPE_USE};
private static final TargetType[] FIELD_TARGETS = {TargetType.FIELD, TargetType.TYPE_USE};
private static final TargetType[] PARAMETER_TARGETS = {TargetType.PARAMETER, TargetType.TYPE_USE};
private static final TargetType[] LOCAL_VARIABLE_TARGETS = {TargetType.LOCAL_VARIABLE, TargetType.TYPE_USE};
@NotNull
public static PsiAnnotation.TargetType[] getTargetsForLocation(@Nullable PsiAnnotationOwner owner) {
public static TargetType[] getTargetsForLocation(@Nullable PsiAnnotationOwner owner) {
if (owner == null) {
return PsiAnnotation.TargetType.EMPTY_ARRAY;
return TargetType.EMPTY_ARRAY;
}
if (owner instanceof PsiType || owner instanceof PsiTypeElement) {
@@ -90,24 +90,27 @@ public class AnnotationTargetUtil {
if (element instanceof PsiLocalVariable) {
return LOCAL_VARIABLE_TARGETS;
}
if (element instanceof PsiReceiverParameter) {
return TYPE_USE_TARGETS;
}
}
return PsiAnnotation.TargetType.EMPTY_ARRAY;
return TargetType.EMPTY_ARRAY;
}
@Nullable
public static Set<PsiAnnotation.TargetType> extractRequiredAnnotationTargets(@Nullable PsiAnnotationMemberValue value) {
public static Set<TargetType> extractRequiredAnnotationTargets(@Nullable PsiAnnotationMemberValue value) {
if (value instanceof PsiReference) {
PsiAnnotation.TargetType targetType = translateTargetRef((PsiReference)value);
TargetType targetType = translateTargetRef((PsiReference)value);
if (targetType != null) {
return Collections.singleton(targetType);
}
}
else if (value instanceof PsiArrayInitializerMemberValue) {
Set <PsiAnnotation.TargetType> targets = ContainerUtil.newHashSet();
Set <TargetType> targets = ContainerUtil.newHashSet();
for (PsiAnnotationMemberValue initializer : ((PsiArrayInitializerMemberValue)value).getInitializers()) {
if (initializer instanceof PsiReference) {
PsiAnnotation.TargetType targetType = translateTargetRef((PsiReference)initializer);
TargetType targetType = translateTargetRef((PsiReference)initializer);
if (targetType != null) {
targets.add(targetType);
}
@@ -120,12 +123,12 @@ public class AnnotationTargetUtil {
}
@Nullable
private static PsiAnnotation.TargetType translateTargetRef(@NotNull PsiReference reference) {
private static TargetType translateTargetRef(@NotNull PsiReference reference) {
PsiElement field = reference.resolve();
if (field instanceof PsiEnumConstant) {
String name = ((PsiEnumConstant)field).getName();
try {
return PsiAnnotation.TargetType.valueOf(name);
return TargetType.valueOf(name);
}
catch (IllegalArgumentException e) {
LOG.warn("Unknown target: " + name);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2000-2014 JetBrains s.r.o.
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,10 +19,7 @@ import com.intellij.lang.ASTNode;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.impl.source.Constants;
import com.intellij.psi.impl.source.tree.ChildRole;
import com.intellij.psi.impl.source.tree.CompositeElement;
import com.intellij.psi.impl.source.tree.JavaSourceUtil;
import com.intellij.psi.impl.source.tree.TreeElement;
import com.intellij.psi.impl.source.tree.*;
import com.intellij.psi.tree.ChildRoleBase;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.tree.TokenSet;
@@ -32,7 +29,8 @@ import org.jetbrains.annotations.Nullable;
public class ParameterListElement extends CompositeElement implements Constants {
private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.source.tree.java.ParameterListElement");
private static final TokenSet PARAMETER_SET = TokenSet.create(PARAMETER);
private static final TokenSet PARAMETER_SET = TokenSet.create(JavaElementType.PARAMETER, JavaElementType.RECEIVER_PARAMETER);
public ParameterListElement() {
super(PARAMETER_LIST);
@@ -50,8 +48,10 @@ public class ParameterListElement extends CompositeElement implements Constants
before = Boolean.FALSE;
}
}
TreeElement firstAdded = super.addInternal(first, last, anchor, before);
if (first == last && first.getElementType() == PARAMETER) {
if (first == last && PARAMETER_SET.contains(first.getElementType())) {
JavaSourceUtil.addSeparatingComma(this, first, PARAMETER_SET);
}
@@ -69,9 +69,11 @@ public class ParameterListElement extends CompositeElement implements Constants
public void deleteChildInternal(@NotNull ASTNode child) {
final TreeElement oldLastNodeInsideParens = getLastNodeInsideParens();
final TreeElement oldFirstNodeInsideParens = getFirstNodeInsideParens();
if (child.getElementType() == PARAMETER) {
if (PARAMETER_SET.contains(child.getElementType())) {
JavaSourceUtil.deleteSeparatingComma(this, child);
}
super.deleteChildInternal(child);
// We may want to fix trailing white space processing here - there is a following possible case:
@@ -79,10 +81,11 @@ public class ParameterListElement extends CompositeElement implements Constants
// *) 'arg2' is to be removed;
// We don't want to keep trailing white space then
TreeElement newLastNodeInsideParens = getLastNodeInsideParens();
if (newLastNodeInsideParens != null && newLastNodeInsideParens.getElementType() == WHITE_SPACE) {
if (newLastNodeInsideParens != null && oldLastNodeInsideParens != null && newLastNodeInsideParens.getElementType() == WHITE_SPACE) {
if (oldLastNodeInsideParens.getElementType() != WHITE_SPACE) {
deleteChildInternal(newLastNodeInsideParens);
} else {
}
else {
replaceChild(newLastNodeInsideParens, (ASTNode)oldLastNodeInsideParens.clone());
}
}
@@ -91,7 +94,8 @@ public class ParameterListElement extends CompositeElement implements Constants
if (newFirstNodeInsideParens != null && newFirstNodeInsideParens.getElementType() == WHITE_SPACE) {
if (oldFirstNodeInsideParens == null || oldFirstNodeInsideParens.getElementType() != WHITE_SPACE) {
deleteChildInternal(newFirstNodeInsideParens);
} else {
}
else {
replaceChild(newFirstNodeInsideParens, (ASTNode)oldFirstNodeInsideParens.clone());
}
}
@@ -126,7 +130,7 @@ public class ParameterListElement extends CompositeElement implements Constants
public int getChildRole(ASTNode child) {
LOG.assertTrue(child.getTreeParent() == this);
IElementType i = child.getElementType();
if (i == PARAMETER) {
if (PARAMETER_SET.contains((i))) {
return ChildRole.PARAMETER;
}
else if (i == COMMA) {
@@ -144,7 +148,7 @@ public class ParameterListElement extends CompositeElement implements Constants
}
/**
* @return last node before closing right paren if possible; <code>null</code> otherwise
* @return last node before closing right parenthesis if possible; <code>null</code> otherwise
*/
@Nullable
private TreeElement getLastNodeInsideParens() {
@@ -152,8 +156,8 @@ public class ParameterListElement extends CompositeElement implements Constants
return lastNode.getElementType() == RPARENTH ? lastNode.getTreePrev() : null;
}
/**
* @return first node after opening left paren if possible; <code>null</code> otherwise
/**
* @return first node after opening left parenthesis if possible; <code>null</code> otherwise
*/
@Nullable
private TreeElement getFirstNodeInsideParens() {

View File

@@ -30,6 +30,12 @@ annotation.non.repeatable=The declaration of ''{0}'' does not have a valid java.
annotation.container.wrong.place=Container annotation ''{0}'' must not be present at the same time as the element it contains
annotation.container.not.applicable=Container annotation ''@{0}'' is not applicable to {1}
receiver.wrong.context=Receivers are not allowed outside of method parameter list
receiver.static.context=The receiver cannot be used in a static context
receiver.wrong.position=The receiver should be the first parameter
receiver.type.mismatch=The receiver type does not match the enclosing class type
receiver.name.mismatch=The receiver name does not match the enclosing class type
# These aren't unused.
# suppress inspection "UnusedProperty"
annotation.target.ANNOTATION_TYPE=annotation type
@@ -388,5 +394,6 @@ feature.extension.methods=Extension methods
feature.method.references=Method references
feature.lambda.expressions=Lambda expressions
feature.type.annotations=Type annotations
feature.type.receivers=Receiver parameters
insufficient.language.level={0} are not supported at this language level
bad.qualifier.in.super.method.reference=Bad type qualifier in default super call: redundant interface {0} is extended by {1}

View File

@@ -1,19 +1,3 @@
/*
* Copyright 2000-2012 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.io.*;
import java.util.*;
@@ -47,6 +31,8 @@ class UnsupportedFeatures {
}
}
void f(<error descr="Receiver parameters are not supported at this language level">Object this</error>) { }
interface I {
<error descr="Extension methods are not supported at this language level">default void m() { }</error>
<error descr="Extension methods are not supported at this language level">static void m() { }</error>

View File

@@ -0,0 +1,47 @@
import java.lang.annotation.*;
@interface A { }
@Target(ElementType.TYPE_USE)
@interface TA { }
class C {
@Override
public String toString(@TA C this) { return ""; }
@Override
public boolean equals(@TA C this, @TA Object other) { return false; }
@interface Anno { String f(Anno this); }
void m0() {
try (Object <error descr="Receivers are not allowed outside of method parameter list">this</error>) { }
Runnable r = (C <error descr="Receivers are not allowed outside of method parameter list">C.this</error>) -> { };
}
void m1a(<error descr="Modifier 'final' not allowed here">final</error> C this) { }
void m1b(<error descr="'@A' not applicable to type use">@A</error> C this) { }
void m2(@TA Object other, @TA C <error descr="The receiver should be the first parameter">this</error>) { }
void m3a(@TA <error descr="The receiver type does not match the enclosing class type">Object</error> this) { }
void m3b(@TA <error descr="The receiver type does not match the enclosing class type">int</error> this) { }
void m4a(C C.this) { }
void m4b(C <error descr="The receiver name does not match the enclosing class type">C.X.this</error>) { }
static void sm1(@TA Object <error descr="The receiver cannot be used in a static context">this</error>) { }
C(C <error descr="The receiver cannot be used in a static context">this</error>) { }
static class X {
X(X <error descr="The receiver cannot be used in a static context">this</error>) { }
}
class B {
B(C C.this) { }
B(<error descr="The receiver type does not match the enclosing class type">B</error> C.this, int p) { }
B(C <error descr="The receiver name does not match the enclosing class type">B.this</error>, long p) { }
B(C <error descr="The receiver name does not match the enclosing class type">this</error>, float p) { }
}
}

View File

@@ -130,19 +130,4 @@ class Outer {
IntFunction<Super> f = Outer.<error descr="Annotations are not allowed here">@TA</error> This.super::getField;
}
}
//todo[r.sh]
/*public String toString(@TA C this) { return ""; }
public boolean equals(@TA C this, @TA C other) { return false; }
C(@TA C this, boolean b) { }
class Outer {
class Middle {
class Inner {
void innerMethod(@TA Outer.@TA Middle.@TA Inner this) { }
}
}
}*/
}

View File

@@ -49,6 +49,7 @@ public class AnnotationsHighlightingTest extends LightDaemonAnalyzerTestCase {
public void testTypeAnnotations() { doTest8(false); }
public void testRepeatable() { doTest8(false); }
public void testEnumValues() { doTest8(false); }
public void testReceiverParameters() { doTest8(false); }
private void doTest(boolean checkWarnings) {
setLanguageLevel(LanguageLevel.JDK_1_7);