Report 'nested class accessed via instance reference' error

This commit is contained in:
Svetlana Isakova
2014-08-01 14:02:27 +04:00
parent d150a26f8a
commit 7f33ad90a6
28 changed files with 224 additions and 41 deletions
@@ -591,6 +591,7 @@ public interface Errors {
DiagnosticFactory1<JetSimpleNameExpression, ClassifierDescriptor> NO_CLASS_OBJECT = DiagnosticFactory1.create(ERROR);
DiagnosticFactory1<JetSimpleNameExpression, TypeParameterDescriptor> TYPE_PARAMETER_IS_NOT_AN_EXPRESSION = DiagnosticFactory1.create(ERROR);
DiagnosticFactory1<JetSimpleNameExpression, TypeParameterDescriptor> TYPE_PARAMETER_ON_LHS_OF_DOT = DiagnosticFactory1.create(ERROR);
DiagnosticFactory1<JetExpression, ClassDescriptor> NESTED_CLASS_ACCESSED_VIA_INSTANCE_REFERENCE = DiagnosticFactory1.create(ERROR);
DiagnosticFactory1<PsiElement, ClassDescriptor> INACCESSIBLE_OUTER_CLASS_EXPRESSION = DiagnosticFactory1.create(ERROR);
DiagnosticFactory0<PsiElement> NESTED_CLASS_NOT_ALLOWED = DiagnosticFactory0.create(ERROR);
@@ -259,6 +259,7 @@ public class DefaultErrorMessages {
MAP.put(TYPE_PARAMETER_IS_NOT_AN_EXPRESSION, "Type parameter ''{0}'' is not an expression", NAME);
MAP.put(TYPE_PARAMETER_ON_LHS_OF_DOT, "Type parameter ''{0}'' cannot have or inherit a class object, so it cannot be on the left hand side of dot", NAME);
MAP.put(NO_GENERICS_IN_SUPERTYPE_SPECIFIER, "Generic arguments of the base type must be specified");
MAP.put(NESTED_CLASS_ACCESSED_VIA_INSTANCE_REFERENCE, "Nested {0} accessed via instance reference", RENDER_CLASS_OR_OBJECT_NAME);
MAP.put(INACCESSIBLE_OUTER_CLASS_EXPRESSION, "Expression is inaccessible from a nested class ''{0}'', use ''inner'' keyword to make the class inner", NAME);
MAP.put(NESTED_CLASS_NOT_ALLOWED, "Nested class is not allowed here, use ''inner'' keyword to make the class inner");
@@ -17,5 +17,29 @@
package org.jetbrains.jet.lang.diagnostics.rendering
import org.jetbrains.jet.renderer.Renderer
import org.jetbrains.jet.lang.descriptors.ClassKind
import org.jetbrains.jet.lang.descriptors.ClassKind.*
import org.jetbrains.jet.lang.descriptors.ClassDescriptor
import org.jetbrains.jet.lang.resolve.DescriptorUtils.isClassObject
public fun <P> renderParameter(parameter: P, renderer: Renderer<P>?): Any = renderer?.render(parameter) ?: parameter
public fun ClassDescriptor.renderKindWithName(): String {
val kind = getKind().getText()
if (isClassObject(this)) {
return "$kind of '${getContainingDeclaration().getName()}'"
}
return "$kind '${getName()}'"
}
public fun ClassKind.getText(): String {
return when (this) {
CLASS -> "class"
TRAIT -> "trait"
ENUM_CLASS -> "enum class"
ENUM_ENTRY -> "enum entry"
ANNOTATION_CLASS -> "annotation class"
OBJECT -> "object"
CLASS_OBJECT -> "class object"
}
}
@@ -112,7 +112,14 @@ public class Renderers {
return "Class" + name;
}
return "Object" + name;
}
};
public static final Renderer<ClassDescriptor> RENDER_CLASS_OR_OBJECT_NAME = new Renderer<ClassDescriptor>() {
@NotNull
@Override
public String render(@NotNull ClassDescriptor classifier) {
return RenderingPackage.renderKindWithName(classifier);
}
};
@@ -298,6 +298,7 @@ public class CallExpressionResolver {
JetType selectorReturnType = selectorReturnTypeInfo.getType();
resolveDeferredReceiverInQualifiedExpression(qualifierReceiver, expression, context);
checkNestedClassAccess(expression, context);
//TODO move further
if (expression.getOperationSign() == JetTokens.SAFE_ACCESS) {
@@ -338,4 +339,24 @@ public class CallExpressionResolver {
? context.trace.get(BindingContext.REFERENCE_TARGET, (JetReferenceExpression) calleeExpression) : null;
ReceiversPackage.resolveAsReceiverInQualifiedExpression(qualifierReceiver, context, selectorDescriptor);
}
private static void checkNestedClassAccess(
@NotNull JetQualifiedExpression expression,
@NotNull ExpressionTypingContext context
) {
JetExpression selectorExpression = expression.getSelectorExpression();
if (selectorExpression == null) return;
// A.B - if B is a nested class accessed by outer class, 'A' and 'A.B' were marked as qualifiers
// a.B - if B is a nested class accessed by instance reference, 'a.B' was marked as a qualifier, but 'a' was not (it's an expression)
QualifierReceiver expressionQualifier = context.trace.get(BindingContext.QUALIFIER_RECEIVER, expression);
QualifierReceiver receiverQualifier = context.trace.get(BindingContext.QUALIFIER_RECEIVER, expression.getReceiverExpression());
if (receiverQualifier == null && expressionQualifier != null) {
assert expressionQualifier.getClassifier() instanceof ClassDescriptor :
"Only class can (package cannot) be accessed by instance reference: " + expressionQualifier;
context.trace.report(NESTED_CLASS_ACCESSED_VIA_INSTANCE_REFERENCE.on(selectorExpression, (ClassDescriptor)expressionQualifier.getClassifier()));
}
}
}
@@ -38,6 +38,7 @@ import org.jetbrains.jet.lang.resolve.calls.model.*;
import org.jetbrains.jet.lang.resolve.calls.results.ResolutionStatus;
import org.jetbrains.jet.lang.resolve.calls.tasks.ResolutionTask;
import org.jetbrains.jet.lang.resolve.calls.tasks.TaskPrioritizer;
import org.jetbrains.jet.lang.resolve.calls.util.FakeCallableDescriptorForObject;
import org.jetbrains.jet.lang.resolve.scopes.receivers.ExpressionReceiver;
import org.jetbrains.jet.lang.resolve.scopes.receivers.ReceiverValue;
import org.jetbrains.jet.lang.types.*;
@@ -115,6 +116,9 @@ public class CandidateResolver {
}
}
}
if (!checkThisObject(context)) {
candidateCall.addStatus(OTHER_ERROR);
}
List<JetTypeProjection> jetTypeArguments = context.call.getTypeArguments();
if (jetTypeArguments.isEmpty()) {
@@ -167,6 +171,31 @@ public class CandidateResolver {
}
}
private static boolean checkThisObject(@NotNull CallCandidateResolutionContext<?> context) {
MutableResolvedCall<? extends CallableDescriptor> candidateCall = context.candidateCall;
CallableDescriptor candidateDescriptor = candidateCall.getCandidateDescriptor();
ReceiverValue thisObject = candidateCall.getThisObject();
if (thisObject.exists()) {
ClassDescriptor nestedClass = null;
if (candidateDescriptor instanceof ConstructorDescriptor
&& DescriptorUtils.isStaticNestedClass(candidateDescriptor.getContainingDeclaration())) {
nestedClass = (ClassDescriptor) candidateDescriptor.getContainingDeclaration();
}
else if (candidateDescriptor instanceof FakeCallableDescriptorForObject) {
nestedClass = ((FakeCallableDescriptorForObject) candidateDescriptor).getReferencedDescriptor();
}
if (nestedClass != null) {
context.tracing.nestedClassAccessViaInstanceReference(context.trace, nestedClass);
return false;
}
}
assert (thisObject.exists() == (candidateCall.getResultingDescriptor().getExpectedThisObject() != null))
: "Shouldn't happen because of TaskPrioritizer: " + candidateDescriptor;
return true;
}
private static boolean checkOuterClassMemberIsAccessible(@NotNull CallCandidateResolutionContext<?> context) {
// In "this@Outer.foo()" the error will be reported on "this@Outer" instead
if (context.call.getExplicitReceiver().exists() || context.call.getThisObject().exists()) return true;
@@ -207,9 +207,6 @@ import static org.jetbrains.jet.lang.resolve.calls.ValueArgumentsToParametersMap
processFunctionLiteralArguments();
reportUnmappedParameters();
checkReceiverArgument();
assert (candidateCall.getThisObject().exists() == (candidateCall.getResultingDescriptor().getExpectedThisObject() != null))
: "Shouldn't happen because of TaskPrioritizer: " + candidateCall.getCandidateDescriptor();
}
private void processFunctionLiteralArguments() {
@@ -20,10 +20,7 @@ import com.google.common.collect.Sets;
import com.intellij.lang.ASTNode;
import com.intellij.psi.PsiElement;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jet.lang.descriptors.CallableDescriptor;
import org.jetbrains.jet.lang.descriptors.DeclarationDescriptorWithVisibility;
import org.jetbrains.jet.lang.descriptors.ReceiverParameterDescriptor;
import org.jetbrains.jet.lang.descriptors.ValueParameterDescriptor;
import org.jetbrains.jet.lang.descriptors.*;
import org.jetbrains.jet.lang.psi.*;
import org.jetbrains.jet.lang.resolve.BindingTrace;
import org.jetbrains.jet.lang.resolve.calls.inference.*;
@@ -130,6 +127,11 @@ public abstract class AbstractTracingStrategy implements TracingStrategy {
trace.report(CREATING_AN_INSTANCE_OF_ABSTRACT_CLASS.on(call.getCallElement()));
}
@Override
public void nestedClassAccessViaInstanceReference(@NotNull BindingTrace trace, @NotNull ClassDescriptor classDescriptor) {
trace.report(NESTED_CLASS_ACCESSED_VIA_INSTANCE_REFERENCE.on(reference, classDescriptor));
}
@Override
public void unsafeCall(@NotNull BindingTrace trace, @NotNull JetType type, boolean isCallForImplicitInvoke) {
ASTNode callOperationNode = call.getCallOperationNode();
@@ -275,12 +275,6 @@ public class TaskPrioritizer {
@NotNull Call call
) {
for (D descriptor : descriptors) {
if (descriptor instanceof ConstructorDescriptor && DescriptorUtils.isStaticNestedClass(descriptor.getContainingDeclaration())
|| descriptor instanceof FakeCallableDescriptorForObject) {
// We don't want static nested class constructor or class object / object (as callable)
// to be resolved with expectedThisObject
continue;
}
ResolutionCandidate<D> candidate = ResolutionCandidate.create(call, descriptor);
candidate.setThisObject(thisObject);
candidate.setReceiverArgument(receiverParameter);
@@ -17,10 +17,7 @@
package org.jetbrains.jet.lang.resolve.calls.tasks;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jet.lang.descriptors.CallableDescriptor;
import org.jetbrains.jet.lang.descriptors.DeclarationDescriptorWithVisibility;
import org.jetbrains.jet.lang.descriptors.ReceiverParameterDescriptor;
import org.jetbrains.jet.lang.descriptors.ValueParameterDescriptor;
import org.jetbrains.jet.lang.descriptors.*;
import org.jetbrains.jet.lang.psi.Call;
import org.jetbrains.jet.lang.psi.JetFunctionLiteralArgument;
import org.jetbrains.jet.lang.resolve.BindingTrace;
@@ -83,6 +80,9 @@ public interface TracingStrategy {
@Override
public void instantiationOfAbstractClass(@NotNull BindingTrace trace) {}
@Override
public void nestedClassAccessViaInstanceReference(@NotNull BindingTrace trace, @NotNull ClassDescriptor classDescriptor) {}
@Override
public void unsafeCall(@NotNull BindingTrace trace, @NotNull JetType type, boolean isCallForImplicitInvoke) {}
@@ -132,6 +132,8 @@ public interface TracingStrategy {
void instantiationOfAbstractClass(@NotNull BindingTrace trace);
void nestedClassAccessViaInstanceReference(@NotNull BindingTrace trace, @NotNull ClassDescriptor classDescriptor);
void unsafeCall(@NotNull BindingTrace trace, @NotNull JetType type, boolean isCallForImplicitInvoke);
void unnecessarySafeCall(@NotNull BindingTrace trace, @NotNull JetType type);
@@ -591,16 +591,6 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor {
return null;
}
ReceiverValue receiver = new TransientReceiver(lhsType);
TemporaryTraceAndCache temporaryWithReceiver = TemporaryTraceAndCache.create(
context, "trace to resolve callable reference with receiver", reference);
CallableDescriptor descriptor = resolveCallableNotCheckingArguments(
reference, receiver, context.replaceTraceAndCache(temporaryWithReceiver), result);
if (result[0]) {
temporaryWithReceiver.commit();
return descriptor;
}
JetScope staticScope = getStaticNestedClassesScope((ClassDescriptor) classifier);
TemporaryTraceAndCache temporaryForStatic = TemporaryTraceAndCache.create(
context, "trace to resolve callable reference in static scope", reference);
@@ -611,6 +601,16 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor {
return possibleStaticNestedClassConstructor;
}
ReceiverValue receiver = new TransientReceiver(lhsType);
TemporaryTraceAndCache temporaryWithReceiver = TemporaryTraceAndCache.create(
context, "trace to resolve callable reference with receiver", reference);
CallableDescriptor descriptor = resolveCallableNotCheckingArguments(
reference, receiver, context.replaceTraceAndCache(temporaryWithReceiver), result);
if (result[0]) {
temporaryWithReceiver.commit();
return descriptor;
}
return null;
}
@@ -454,6 +454,13 @@ public class ControlStructureTypingUtils {
throwError();
}
@Override
public void nestedClassAccessViaInstanceReference(
@NotNull BindingTrace trace, @NotNull ClassDescriptor classDescriptor
) {
throwError();
}
@Override
public void unsafeCall(
@NotNull BindingTrace trace, @NotNull JetType type, boolean isCallForImplicitInvoke
@@ -13,6 +13,6 @@ class C {
}
fun box(): String {
val res = C().Obj.o + C().Obj.InnerObj.k() + C().Obj.D().ko
val res = C.Obj.o + C.Obj.InnerObj.k() + C.Obj.D().ko
return if (res == "OKKO") "OK" else "Fail: $res"
}
@@ -1,5 +1,5 @@
fun main(args: Array<String>) {
val str = aaa.A().E.A
val str = aaa.A.E.A
if (str.toString() != "A") {
throw Exception()
}
@@ -1,5 +1,5 @@
fun main(args: Array<String>) {
val str = aaa.A().O.s
val str = aaa.A.O.s
if (str != "OK") {
throw Exception()
}
@@ -23,6 +23,6 @@ class Outer {
fun Outer.foo() {
Outer()
<!UNRESOLVED_REFERENCE!>Nested<!>()
<!NESTED_CLASS_ACCESSED_VIA_INSTANCE_REFERENCE!>Nested<!>()
Inner()
}
@@ -0,0 +1,54 @@
trait N { fun foo() = 1 }
class WithClassObject {
class object {}
class Nested()
class NestedWithClassObject { class object : N }
enum class NestedEnum { A }
object NestedObj : N { fun invoke() = 1 }
}
class WithoutClassObject {
class Nested()
class NestedWithClassObject { class object : N }
enum class NestedEnum { A }
object NestedObj : N { fun invoke() = 1 }
}
object Obj {
class Nested()
class NestedWithClassObject { class object : N }
enum class NestedEnum { A }
object NestedObj : N { fun invoke() = 1 }
}
fun test(with: WithClassObject, without: WithoutClassObject, obj: Obj) {
with.<!NESTED_CLASS_ACCESSED_VIA_INSTANCE_REFERENCE!>Nested<!>()
with.<!NESTED_CLASS_ACCESSED_VIA_INSTANCE_REFERENCE!>NestedWithClassObject<!>
with.<!NESTED_CLASS_ACCESSED_VIA_INSTANCE_REFERENCE!>NestedWithClassObject<!>()
with.<!NESTED_CLASS_ACCESSED_VIA_INSTANCE_REFERENCE!>NestedWithClassObject<!>.foo()
with.<!NESTED_CLASS_ACCESSED_VIA_INSTANCE_REFERENCE!>NestedEnum<!>.A
with.<!NESTED_CLASS_ACCESSED_VIA_INSTANCE_REFERENCE!>NestedObj<!>
with.<!NESTED_CLASS_ACCESSED_VIA_INSTANCE_REFERENCE!>NestedObj<!>()
with.<!NESTED_CLASS_ACCESSED_VIA_INSTANCE_REFERENCE!>NestedObj<!>.foo()
without.<!NESTED_CLASS_ACCESSED_VIA_INSTANCE_REFERENCE!>Nested<!>()
without.<!NESTED_CLASS_ACCESSED_VIA_INSTANCE_REFERENCE!>NestedWithClassObject<!>
without.<!NESTED_CLASS_ACCESSED_VIA_INSTANCE_REFERENCE!>NestedWithClassObject<!>()
without.<!NESTED_CLASS_ACCESSED_VIA_INSTANCE_REFERENCE!>NestedWithClassObject<!>.foo()
without.<!NESTED_CLASS_ACCESSED_VIA_INSTANCE_REFERENCE!>NestedEnum<!>.A
without.<!NESTED_CLASS_ACCESSED_VIA_INSTANCE_REFERENCE!>NestedObj<!>
without.<!NESTED_CLASS_ACCESSED_VIA_INSTANCE_REFERENCE!>NestedObj<!>()
without.<!NESTED_CLASS_ACCESSED_VIA_INSTANCE_REFERENCE!>NestedObj<!>.foo()
obj.<!NESTED_CLASS_ACCESSED_VIA_INSTANCE_REFERENCE!>Nested<!>()
obj.<!NESTED_CLASS_ACCESSED_VIA_INSTANCE_REFERENCE!>NestedWithClassObject<!>
obj.<!NESTED_CLASS_ACCESSED_VIA_INSTANCE_REFERENCE!>NestedWithClassObject<!>()
obj.<!NESTED_CLASS_ACCESSED_VIA_INSTANCE_REFERENCE!>NestedWithClassObject<!>.foo()
obj.<!NESTED_CLASS_ACCESSED_VIA_INSTANCE_REFERENCE!>NestedEnum<!>.A
obj.<!NESTED_CLASS_ACCESSED_VIA_INSTANCE_REFERENCE!>NestedObj<!>
obj.<!NESTED_CLASS_ACCESSED_VIA_INSTANCE_REFERENCE!>NestedObj<!>()
obj.<!NESTED_CLASS_ACCESSED_VIA_INSTANCE_REFERENCE!>NestedObj<!>.foo()
}
@@ -0,0 +1,12 @@
//no nested class access via instance reference error
fun test() {
A.f(<!TYPE_MISMATCH!>""<!>)
}
class A() {
class object {
object f {
fun invoke(i: Int) = i
}
}
}
@@ -6,7 +6,7 @@ class A {
}
fun A.main() {
::<!UNRESOLVED_REFERENCE!>Nested<!>
::<!NESTED_CLASS_ACCESSED_VIA_INSTANCE_REFERENCE!>Nested<!>
val y = A::Nested
y : KFunction0<A.Nested>
@@ -4944,6 +4944,11 @@ public class JetDiagnosticsTestGenerated extends AbstractJetDiagnosticsTest {
doTest("compiler/testData/diagnostics/tests/inner/modality.kt");
}
@TestMetadata("nestedClassAccessedViaInstanceReference.kt")
public void testNestedClassAccessedViaInstanceReference() throws Exception {
doTest("compiler/testData/diagnostics/tests/inner/nestedClassAccessedViaInstanceReference.kt");
}
@TestMetadata("nestedClassExtendsOuter.kt")
public void testNestedClassExtendsOuter() throws Exception {
doTest("compiler/testData/diagnostics/tests/inner/nestedClassExtendsOuter.kt");
@@ -5896,6 +5901,11 @@ public class JetDiagnosticsTestGenerated extends AbstractJetDiagnosticsTest {
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/diagnostics/tests/objects"), Pattern.compile("^(.+)\\.kt$"), true);
}
@TestMetadata("invokeOnInnerObject.kt")
public void testInvokeOnInnerObject() throws Exception {
doTest("compiler/testData/diagnostics/tests/objects/invokeOnInnerObject.kt");
}
@TestMetadata("kt2240.kt")
public void testKt2240() throws Exception {
doTest("compiler/testData/diagnostics/tests/objects/kt2240.kt");
@@ -6,5 +6,5 @@ object A {
fun test<T>(a: T) {
val c = (a as A)
c.<error descr="[FUNCTION_EXPECTED] Expression 'B' of type '[Package-type B]' cannot be invoked as a function. The function invoke() is not found"><error>B</error></error>()
c.<error descr="[NESTED_CLASS_ACCESSED_VIA_INSTANCE_REFERENCE] Nested class 'B' accessed via instance reference">B</error>()
}
@@ -0,0 +1,16 @@
// !DIAGNOSTICS_NUMBER: 4
// !DIAGNOSTICS: NESTED_CLASS_ACCESSED_VIA_INSTANCE_REFERENCE
class A {
class Nested()
class NestedWithClassObject { class object }
enum class NestedEnum { A }
object NestedObj { fun invoke() = 1 }
}
fun test(a: A) {
a.Nested()
a.NestedWithClassObject
a.NestedEnum
a.NestedObj
}
@@ -0,0 +1,2 @@
<!-- nestedClassAcessedViaInstanceReference1 -->
Nested class 'Nested' accessed via instance reference
@@ -0,0 +1,2 @@
<!-- nestedClassAcessedViaInstanceReference2 -->
Nested class object of 'NestedWithClassObject' accessed via instance reference
@@ -0,0 +1,2 @@
<!-- nestedClassAcessedViaInstanceReference3 -->
Nested enum class 'NestedEnum' accessed via instance reference
@@ -0,0 +1,2 @@
<!-- nestedClassAcessedViaInstanceReference4 -->
Nested object 'NestedObj' accessed via instance reference
@@ -98,6 +98,11 @@ public class DiagnosticMessageTestGenerated extends AbstractDiagnosticMessageTes
doTest("idea/testData/diagnosticMessage/nameInConstraintIsNotATypeParameter.kt");
}
@TestMetadata("nestedClassAcessedViaInstanceReference.kt")
public void testNestedClassAcessedViaInstanceReference() throws Exception {
doTest("idea/testData/diagnosticMessage/nestedClassAcessedViaInstanceReference.kt");
}
@TestMetadata("noneApplicable.kt")
public void testNoneApplicable() throws Exception {
doTest("idea/testData/diagnosticMessage/noneApplicable.kt");
@@ -5,32 +5,25 @@ enum class A {
BAR : A() {
fun explicitFromEntry() = A.FOO
//fun byThisFromEntry() = this.FOO
fun implicitFromEntry() = FOO
}
fun explicit() = A.FOO
fun byThis() = this.FOO
fun implicit() = FOO
}
fun A.extExplicit() = A.FOO
fun A.extByThis() = this.FOO
//fun A.extImplicit() = FOO
fun box(): String {
assertEquals(A.FOO, A.FOO.explicit(), "explicit access")
assertEquals(A.FOO, A.FOO.byThis(), "access by this")
assertEquals(A.FOO, A.FOO.implicit(), "implicit access")
assertEquals(A.FOO, A.FOO.explicit(), "explicit access from BAR")
// TODO uncoment when KT-4692 will be fixed
//assertEquals(A.FOO, A.FOO.byThis(), "access by this from BAR")
assertEquals(A.FOO, A.FOO.implicit(), "implicit access from BAR")
assertEquals(A.FOO, A.FOO.extExplicit(), "explicit access from ext fun")
assertEquals(A.FOO, A.FOO.extByThis(), "access by this from ext fun")
// TODO uncoment when KT-4692 will be fixed
// TODO uncoment when KT-5605 will be fixed
//assertEquals(A.FOO, A.FOO.extImplicit(), "implicit access from ext fun")
return "OK"