From 244fc31da89625fb94df87d25bd5edaf296e0711 Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Tue, 26 Apr 2011 18:47:16 +0400 Subject: [PATCH] JET-16 Check property initializers as parts of the primary constructor's body --- .../org/jetbrains/jet/lang/psi/JetClass.java | 7 + .../jetbrains/jet/lang/psi/JetClassBody.java | 5 + .../jet/lang/resolve/TopDownAnalyzer.java | 151 ++++++++++++------ idea/testData/checker/Constructors.jet | 19 ++- idea/testData/checker/QualifiedThis.jet | 2 +- .../infos/PropertiesWithBackingFields.jet | 2 +- idea/testData/codegen/privateVal.jet | 2 +- idea/testData/codegen/privateVar.jet | 2 +- .../jet/codegen/PropertyGenTest.java | 6 +- 9 files changed, 135 insertions(+), 61 deletions(-) diff --git a/idea/src/org/jetbrains/jet/lang/psi/JetClass.java b/idea/src/org/jetbrains/jet/lang/psi/JetClass.java index 33e3c243c0f..c1452cba77f 100644 --- a/idea/src/org/jetbrains/jet/lang/psi/JetClass.java +++ b/idea/src/org/jetbrains/jet/lang/psi/JetClass.java @@ -78,4 +78,11 @@ public class JetClass extends JetTypeParameterListOwner implements JetClassOrObj public boolean hasPrimaryConstructor() { return getPrimaryConstructorParameterList() != null; } + + public List getProperties() { + JetClassBody body = (JetClassBody) findChildByType(JetNodeTypes.CLASS_BODY); + if (body == null) return Collections.emptyList(); + + return body.getProperties(); + } } diff --git a/idea/src/org/jetbrains/jet/lang/psi/JetClassBody.java b/idea/src/org/jetbrains/jet/lang/psi/JetClassBody.java index 693dd3da6ef..a24af493a04 100644 --- a/idea/src/org/jetbrains/jet/lang/psi/JetClassBody.java +++ b/idea/src/org/jetbrains/jet/lang/psi/JetClassBody.java @@ -32,4 +32,9 @@ public class JetClassBody extends JetElement { public List getAnonymousInitializers() { return findChildrenByType(JetNodeTypes.ANONYMOUS_INITIALIZER); } + + @NotNull + public List getProperties() { + return findChildrenByType(JetNodeTypes.PROPERTY); + } } diff --git a/idea/src/org/jetbrains/jet/lang/resolve/TopDownAnalyzer.java b/idea/src/org/jetbrains/jet/lang/resolve/TopDownAnalyzer.java index 69c6c98aee2..c24d579b78e 100644 --- a/idea/src/org/jetbrains/jet/lang/resolve/TopDownAnalyzer.java +++ b/idea/src/org/jetbrains/jet/lang/resolve/TopDownAnalyzer.java @@ -3,6 +3,7 @@ package org.jetbrains.jet.lang.resolve; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Maps; import com.google.common.collect.Multimap; +import com.google.common.collect.Sets; import com.intellij.psi.util.PsiTreeUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.jet.lang.JetSemanticServices; @@ -273,6 +274,7 @@ public class TopDownAnalyzer { private void processProperty(WritableScope declaringScope, JetProperty property) { declaringScopes.put(property, declaringScope); + // TODO : Do not infer the type from the initializer here: the scope is wrong, and not ready anyway PropertyDescriptor descriptor = classDescriptorResolver.resolvePropertyDescriptor(declaringScope.getContainingDeclaration(), declaringScope, property); declaringScope.addVariableDescriptor(descriptor); declaringScopesToProperties.put(declaringScope.getContainingDeclaration(), descriptor); @@ -289,12 +291,9 @@ public class TopDownAnalyzer { resolveDelegationSpecifierLists(); resolveAnonymousInitializers(); + resolvePropertyDeclarationBodies(); resolveSecondaryConstructorBodies(); - - //TODO : anonymous initializers - - resolvePropertyDeclarationBodies(); resolveFunctionBodies(); } @@ -481,69 +480,115 @@ public class TopDownAnalyzer { } private void resolvePropertyDeclarationBodies() { - for (Map.Entry entry : properties.entrySet()) { - JetProperty declaration = entry.getKey(); - final PropertyDescriptor propertyDescriptor = entry.getValue(); - WritableScope declaringScope = declaringScopes.get(declaration); - JetExpression initializer = declaration.getInitializer(); - if (initializer != null) { - JetFlowInformationProvider flowInformationProvider = computeFlowData(declaration, initializer); - JetTypeInferrer typeInferrer = semanticServices.getTypeInferrer(trace, flowInformationProvider); - JetType type = typeInferrer.getType(declaringScope, initializer, false); + // Member properties + Set processed = Sets.newHashSet(); + for (Map.Entry entry : classes.entrySet()) { + JetClass jetClass = entry.getKey(); + MutableClassDescriptor classDescriptor = entry.getValue(); - JetType expectedType; - PropertySetterDescriptor setter = propertyDescriptor.getSetter(); - if (setter != null) { - expectedType = setter.getUnsubstitutedReturnType(); - } - else { - expectedType = propertyDescriptor.getInType(); - if (expectedType == null) { - expectedType = propertyDescriptor.getOutType(); + for (JetProperty property : jetClass.getProperties()) { + final PropertyDescriptor propertyDescriptor = properties.get(property); + assert propertyDescriptor != null; + + WritableScope declaringScope = declaringScopes.get(property); + + JetExpression initializer = property.getInitializer(); + if (initializer != null) { + ConstructorDescriptor primaryConstructor = classDescriptor.getUnsubstitutedPrimaryConstructor(); + if (primaryConstructor == null) { + semanticServices.getErrorHandler().genericError(initializer.getNode(), "Property initializers are not allowed when no primary constructor is present"); + } + else { + JetScope scope = getInnerScopeForConstructor(primaryConstructor, classDescriptor.getWritableUnsubstitutedMemberScope()); + resolvePropertyInitializer(property, propertyDescriptor, initializer, scope); } } - if (type != null && expectedType != null - && !semanticServices.getTypeChecker().isConvertibleTo(type, expectedType)) { - semanticServices.getErrorHandler().typeMismatch(initializer, expectedType, type); - } + + resolvePropertyAccessors(property, propertyDescriptor, declaringScope); + processed.add(property); + } + } + + // Top-level properties + for (Map.Entry entry : properties.entrySet()) { + JetProperty property = entry.getKey(); + if (processed.contains(property)) continue; + + final PropertyDescriptor propertyDescriptor = entry.getValue(); + WritableScope declaringScope = declaringScopes.get(property); + + JetExpression initializer = property.getInitializer(); + if (initializer != null) { + resolvePropertyInitializer(property, propertyDescriptor, initializer, declaringScope); } - BindingTraceAdapter fieldAccessTrackingTrace = new BindingTraceAdapter(trace) { - @Override - public void recordReferenceResolution(@NotNull JetReferenceExpression expression, @NotNull DeclarationDescriptor descriptor) { - super.recordReferenceResolution(expression, descriptor); - if (expression instanceof JetSimpleNameExpression) { - JetSimpleNameExpression simpleNameExpression = (JetSimpleNameExpression) expression; - if (simpleNameExpression.getReferencedNameElementType() == JetTokens.FIELD_IDENTIFIER) { - // This check may be considered redundant as long as $x is only accessible from accessors to $x - if (descriptor == propertyDescriptor) { // TODO : original? - recordFieldAccessFromAccessor(propertyDescriptor); - } + resolvePropertyAccessors(property, propertyDescriptor, declaringScope); + } + } + + private void resolvePropertyAccessors(JetProperty property, PropertyDescriptor propertyDescriptor, WritableScope declaringScope) { + BindingTraceAdapter fieldAccessTrackingTrace = createFieldTrackingTrace(propertyDescriptor); + + WritableScope accessorScope = semanticServices.createWritableScope(declaringScope, declaringScope.getContainingDeclaration()); + accessorScope.addPropertyDescriptorByFieldName("$" + propertyDescriptor.getName(), propertyDescriptor); + + JetPropertyAccessor getter = property.getGetter(); + PropertyGetterDescriptor getterDescriptor = propertyDescriptor.getGetter(); + if (getter != null && getterDescriptor != null) { + resolveFunctionBody(fieldAccessTrackingTrace, getter, getterDescriptor, accessorScope); + } + + JetPropertyAccessor setter = property.getSetter(); + PropertySetterDescriptor setterDescriptor = propertyDescriptor.getSetter(); + if (setter != null && setterDescriptor != null) { + resolveFunctionBody(fieldAccessTrackingTrace, setter, setterDescriptor, accessorScope); + } + + JetExpression initializer = property.getInitializer(); + if (!property.isVar() && initializer != null && !trace.hasBackingField(propertyDescriptor)) { + semanticServices.getErrorHandler().genericError(initializer.getNode(), "Initializer is not allowed here because this property has no setter and no backing field either"); + } + } + + private BindingTraceAdapter createFieldTrackingTrace(final PropertyDescriptor propertyDescriptor) { + return new BindingTraceAdapter(trace) { + @Override + public void recordReferenceResolution(@NotNull JetReferenceExpression expression, @NotNull DeclarationDescriptor descriptor) { + super.recordReferenceResolution(expression, descriptor); + if (expression instanceof JetSimpleNameExpression) { + JetSimpleNameExpression simpleNameExpression = (JetSimpleNameExpression) expression; + if (simpleNameExpression.getReferencedNameElementType() == JetTokens.FIELD_IDENTIFIER) { + // This check may be considered redundant as long as $x is only accessible from accessors to $x + if (descriptor == propertyDescriptor) { // TODO : original? + recordFieldAccessFromAccessor(propertyDescriptor); } } } - }; - - WritableScope accessorScope = semanticServices.createWritableScope(declaringScope, declaringScope.getContainingDeclaration()); - accessorScope.addPropertyDescriptorByFieldName("$" + propertyDescriptor.getName(), propertyDescriptor); - - JetPropertyAccessor getter = declaration.getGetter(); - PropertyGetterDescriptor getterDescriptor = propertyDescriptor.getGetter(); - if (getter != null && getterDescriptor != null) { - resolveFunctionBody(fieldAccessTrackingTrace, getter, getterDescriptor, accessorScope); } + }; + } - JetPropertyAccessor setter = declaration.getSetter(); - PropertySetterDescriptor setterDescriptor = propertyDescriptor.getSetter(); - if (setter != null && setterDescriptor != null) { - resolveFunctionBody(fieldAccessTrackingTrace, setter, setterDescriptor, accessorScope); - } + private void resolvePropertyInitializer(JetProperty property, PropertyDescriptor propertyDescriptor, JetExpression initializer, JetScope scope) { + JetFlowInformationProvider flowInformationProvider = computeFlowData(property, initializer); // TODO : flow JET-15 + JetTypeInferrer typeInferrer = semanticServices.getTypeInferrer(trace, flowInformationProvider); + JetType type = typeInferrer.getType(scope, initializer, false); - if (!declaration.isVar() && initializer != null && !trace.hasBackingField(propertyDescriptor)) { - semanticServices.getErrorHandler().genericError(initializer.getNode(), "Initializer is not allowed here because this property has no setter and no backing field either"); + JetType expectedType; + PropertySetterDescriptor setter = propertyDescriptor.getSetter(); + if (setter != null) { + expectedType = setter.getUnsubstitutedReturnType(); + } + else { + expectedType = propertyDescriptor.getInType(); + if (expectedType == null) { + expectedType = propertyDescriptor.getOutType(); } } + if (type != null && expectedType != null + && !semanticServices.getTypeChecker().isConvertibleTo(type, expectedType)) { + semanticServices.getErrorHandler().typeMismatch(initializer, expectedType, type); + } } private void resolveFunctionBodies() { diff --git a/idea/testData/checker/Constructors.jet b/idea/testData/checker/Constructors.jet index f66ee628103..e79ff9c3da6 100644 --- a/idea/testData/checker/Constructors.jet +++ b/idea/testData/checker/Constructors.jet @@ -31,4 +31,21 @@ class WithPC1(a : Int) { class Foo() : WithPC0, this() { -} \ No newline at end of file +} + +class WithCPI_Dup(x : Int) { + var x : Int +} + +class WithCPI(x : Int) { + val a = 1 + val b : Int = $a + val xy : Int = x +} + +class NoCPI { + val a = 1 + var ab = 1 + get() = 1 + set(v) {} +} diff --git a/idea/testData/checker/QualifiedThis.jet b/idea/testData/checker/QualifiedThis.jet index 1da9fc873df..dc155731740 100644 --- a/idea/testData/checker/QualifiedThis.jet +++ b/idea/testData/checker/QualifiedThis.jet @@ -4,7 +4,7 @@ class Dup { } } -class A { +class A() { fun foo() : Unit { this@A this@a diff --git a/idea/testData/checker/infos/PropertiesWithBackingFields.jet b/idea/testData/checker/infos/PropertiesWithBackingFields.jet index 0a1d8911788..0890c3910a7 100644 --- a/idea/testData/checker/infos/PropertiesWithBackingFields.jet +++ b/idea/testData/checker/infos/PropertiesWithBackingFields.jet @@ -1,4 +1,4 @@ -class Test { +class Test() { abstract val x : Int abstract val x1 : Int get abstract val x2 : Int get() = 1 diff --git a/idea/testData/codegen/privateVal.jet b/idea/testData/codegen/privateVal.jet index 622e6348c6d..dd037bfec8d 100644 --- a/idea/testData/codegen/privateVal.jet +++ b/idea/testData/codegen/privateVal.jet @@ -1,3 +1,3 @@ -class PrivateVal { +class PrivateVal() { private val prop = 0; } diff --git a/idea/testData/codegen/privateVar.jet b/idea/testData/codegen/privateVar.jet index 64cd7816373..ef291e0d9ce 100644 --- a/idea/testData/codegen/privateVar.jet +++ b/idea/testData/codegen/privateVar.jet @@ -1,4 +1,4 @@ -class PrivateVar { +class PrivateVar() { private var x = 0; fun setValueOfX(aValue: Int) { x = aValue } diff --git a/idea/tests/org/jetbrains/jet/codegen/PropertyGenTest.java b/idea/tests/org/jetbrains/jet/codegen/PropertyGenTest.java index 768caa3199e..34047039861 100644 --- a/idea/tests/org/jetbrains/jet/codegen/PropertyGenTest.java +++ b/idea/tests/org/jetbrains/jet/codegen/PropertyGenTest.java @@ -30,7 +30,7 @@ public class PropertyGenTest extends CodegenTestCase { } public void testPublicVar() throws Exception { - loadText("class PublicVar { public var foo = 0; }"); + loadText("class PublicVar() { public var foo = 0; }"); System.out.println(generateToText()); final Class aClass = loadImplementationClass(generateClassesInFile(), "PublicVar"); final Object instance = aClass.newInstance(); @@ -41,7 +41,7 @@ public class PropertyGenTest extends CodegenTestCase { } public void testAccessorsInInterface() { - loadText("class AccessorsInInterface { public var foo = 0; }"); + loadText("class AccessorsInInterface() { public var foo = 0; }"); final Class aClass = loadClass("AccessorsInInterface", generateClassesInFile()); assertNotNull(findMethodByName(aClass, "getFoo")); assertNotNull(findMethodByName(aClass, "setFoo")); @@ -82,7 +82,7 @@ public class PropertyGenTest extends CodegenTestCase { } public void testAccessorsWithoutBody() throws Exception { - loadText("class AccessorsWithoutBody { public var foo: Int = 349\n get\n private set\n fun setter() { foo = 610; } } "); + loadText("class AccessorsWithoutBody() { public var foo: Int = 349\n get\n private set\n fun setter() { foo = 610; } } "); final Class aClass = loadImplementationClass(generateClassesInFile(), "AccessorsWithoutBody"); final Object instance = aClass.newInstance(); final Method getFoo = findMethodByName(aClass, "getFoo");