JET-16 Check property initializers as parts of the primary constructor's body
This commit is contained in:
@@ -78,4 +78,11 @@ public class JetClass extends JetTypeParameterListOwner implements JetClassOrObj
|
|||||||
public boolean hasPrimaryConstructor() {
|
public boolean hasPrimaryConstructor() {
|
||||||
return getPrimaryConstructorParameterList() != null;
|
return getPrimaryConstructorParameterList() != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<JetProperty> getProperties() {
|
||||||
|
JetClassBody body = (JetClassBody) findChildByType(JetNodeTypes.CLASS_BODY);
|
||||||
|
if (body == null) return Collections.emptyList();
|
||||||
|
|
||||||
|
return body.getProperties();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,4 +32,9 @@ public class JetClassBody extends JetElement {
|
|||||||
public List<JetClassInitializer> getAnonymousInitializers() {
|
public List<JetClassInitializer> getAnonymousInitializers() {
|
||||||
return findChildrenByType(JetNodeTypes.ANONYMOUS_INITIALIZER);
|
return findChildrenByType(JetNodeTypes.ANONYMOUS_INITIALIZER);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public List<JetProperty> getProperties() {
|
||||||
|
return findChildrenByType(JetNodeTypes.PROPERTY);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package org.jetbrains.jet.lang.resolve;
|
|||||||
import com.google.common.collect.ArrayListMultimap;
|
import com.google.common.collect.ArrayListMultimap;
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
import com.google.common.collect.Multimap;
|
import com.google.common.collect.Multimap;
|
||||||
|
import com.google.common.collect.Sets;
|
||||||
import com.intellij.psi.util.PsiTreeUtil;
|
import com.intellij.psi.util.PsiTreeUtil;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.jet.lang.JetSemanticServices;
|
import org.jetbrains.jet.lang.JetSemanticServices;
|
||||||
@@ -273,6 +274,7 @@ public class TopDownAnalyzer {
|
|||||||
|
|
||||||
private void processProperty(WritableScope declaringScope, JetProperty property) {
|
private void processProperty(WritableScope declaringScope, JetProperty property) {
|
||||||
declaringScopes.put(property, declaringScope);
|
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);
|
PropertyDescriptor descriptor = classDescriptorResolver.resolvePropertyDescriptor(declaringScope.getContainingDeclaration(), declaringScope, property);
|
||||||
declaringScope.addVariableDescriptor(descriptor);
|
declaringScope.addVariableDescriptor(descriptor);
|
||||||
declaringScopesToProperties.put(declaringScope.getContainingDeclaration(), descriptor);
|
declaringScopesToProperties.put(declaringScope.getContainingDeclaration(), descriptor);
|
||||||
@@ -289,12 +291,9 @@ public class TopDownAnalyzer {
|
|||||||
resolveDelegationSpecifierLists();
|
resolveDelegationSpecifierLists();
|
||||||
|
|
||||||
resolveAnonymousInitializers();
|
resolveAnonymousInitializers();
|
||||||
|
resolvePropertyDeclarationBodies();
|
||||||
|
|
||||||
resolveSecondaryConstructorBodies();
|
resolveSecondaryConstructorBodies();
|
||||||
|
|
||||||
//TODO : anonymous initializers
|
|
||||||
|
|
||||||
resolvePropertyDeclarationBodies();
|
|
||||||
resolveFunctionBodies();
|
resolveFunctionBodies();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -481,69 +480,115 @@ public class TopDownAnalyzer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void resolvePropertyDeclarationBodies() {
|
private void resolvePropertyDeclarationBodies() {
|
||||||
for (Map.Entry<JetProperty, PropertyDescriptor> entry : properties.entrySet()) {
|
|
||||||
JetProperty declaration = entry.getKey();
|
|
||||||
final PropertyDescriptor propertyDescriptor = entry.getValue();
|
|
||||||
WritableScope declaringScope = declaringScopes.get(declaration);
|
|
||||||
|
|
||||||
JetExpression initializer = declaration.getInitializer();
|
// Member properties
|
||||||
if (initializer != null) {
|
Set<JetProperty> processed = Sets.newHashSet();
|
||||||
JetFlowInformationProvider flowInformationProvider = computeFlowData(declaration, initializer);
|
for (Map.Entry<JetClass, MutableClassDescriptor> entry : classes.entrySet()) {
|
||||||
JetTypeInferrer typeInferrer = semanticServices.getTypeInferrer(trace, flowInformationProvider);
|
JetClass jetClass = entry.getKey();
|
||||||
JetType type = typeInferrer.getType(declaringScope, initializer, false);
|
MutableClassDescriptor classDescriptor = entry.getValue();
|
||||||
|
|
||||||
JetType expectedType;
|
for (JetProperty property : jetClass.getProperties()) {
|
||||||
PropertySetterDescriptor setter = propertyDescriptor.getSetter();
|
final PropertyDescriptor propertyDescriptor = properties.get(property);
|
||||||
if (setter != null) {
|
assert propertyDescriptor != null;
|
||||||
expectedType = setter.getUnsubstitutedReturnType();
|
|
||||||
}
|
WritableScope declaringScope = declaringScopes.get(property);
|
||||||
else {
|
|
||||||
expectedType = propertyDescriptor.getInType();
|
JetExpression initializer = property.getInitializer();
|
||||||
if (expectedType == null) {
|
if (initializer != null) {
|
||||||
expectedType = propertyDescriptor.getOutType();
|
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)) {
|
resolvePropertyAccessors(property, propertyDescriptor, declaringScope);
|
||||||
semanticServices.getErrorHandler().typeMismatch(initializer, expectedType, type);
|
processed.add(property);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Top-level properties
|
||||||
|
for (Map.Entry<JetProperty, PropertyDescriptor> 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) {
|
resolvePropertyAccessors(property, propertyDescriptor, declaringScope);
|
||||||
@Override
|
}
|
||||||
public void recordReferenceResolution(@NotNull JetReferenceExpression expression, @NotNull DeclarationDescriptor descriptor) {
|
}
|
||||||
super.recordReferenceResolution(expression, descriptor);
|
|
||||||
if (expression instanceof JetSimpleNameExpression) {
|
private void resolvePropertyAccessors(JetProperty property, PropertyDescriptor propertyDescriptor, WritableScope declaringScope) {
|
||||||
JetSimpleNameExpression simpleNameExpression = (JetSimpleNameExpression) expression;
|
BindingTraceAdapter fieldAccessTrackingTrace = createFieldTrackingTrace(propertyDescriptor);
|
||||||
if (simpleNameExpression.getReferencedNameElementType() == JetTokens.FIELD_IDENTIFIER) {
|
|
||||||
// This check may be considered redundant as long as $x is only accessible from accessors to $x
|
WritableScope accessorScope = semanticServices.createWritableScope(declaringScope, declaringScope.getContainingDeclaration());
|
||||||
if (descriptor == propertyDescriptor) { // TODO : original?
|
accessorScope.addPropertyDescriptorByFieldName("$" + propertyDescriptor.getName(), propertyDescriptor);
|
||||||
recordFieldAccessFromAccessor(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();
|
private void resolvePropertyInitializer(JetProperty property, PropertyDescriptor propertyDescriptor, JetExpression initializer, JetScope scope) {
|
||||||
PropertySetterDescriptor setterDescriptor = propertyDescriptor.getSetter();
|
JetFlowInformationProvider flowInformationProvider = computeFlowData(property, initializer); // TODO : flow JET-15
|
||||||
if (setter != null && setterDescriptor != null) {
|
JetTypeInferrer typeInferrer = semanticServices.getTypeInferrer(trace, flowInformationProvider);
|
||||||
resolveFunctionBody(fieldAccessTrackingTrace, setter, setterDescriptor, accessorScope);
|
JetType type = typeInferrer.getType(scope, initializer, false);
|
||||||
}
|
|
||||||
|
|
||||||
if (!declaration.isVar() && initializer != null && !trace.hasBackingField(propertyDescriptor)) {
|
JetType expectedType;
|
||||||
semanticServices.getErrorHandler().genericError(initializer.getNode(), "Initializer is not allowed here because this property has no setter and no backing field either");
|
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() {
|
private void resolveFunctionBodies() {
|
||||||
|
|||||||
@@ -31,4 +31,21 @@ class WithPC1(a : Int) {
|
|||||||
|
|
||||||
class Foo() : <error>WithPC0</error>, <error>this</error>() {
|
class Foo() : <error>WithPC0</error>, <error>this</error>() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class WithCPI_Dup(<error>x : Int</error>) {
|
||||||
|
<error>var x : Int</error>
|
||||||
|
}
|
||||||
|
|
||||||
|
class WithCPI(x : Int) {
|
||||||
|
val a = 1
|
||||||
|
val b : Int = $a
|
||||||
|
val xy : Int = x
|
||||||
|
}
|
||||||
|
|
||||||
|
class NoCPI {
|
||||||
|
val a = <error>1</error>
|
||||||
|
var ab = <error>1</error>
|
||||||
|
get() = 1
|
||||||
|
set(v) {}
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ class Dup {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class A {
|
class A() {
|
||||||
fun foo() : Unit {
|
fun foo() : Unit {
|
||||||
this@A
|
this@A
|
||||||
this<error>@a</error>
|
this<error>@a</error>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
class Test {
|
class Test() {
|
||||||
<info>abstract</info> val x : Int
|
<info>abstract</info> val x : Int
|
||||||
<info>abstract</info> val x1 : Int <info>get</info>
|
<info>abstract</info> val x1 : Int <info>get</info>
|
||||||
<info>abstract</info> val x2 : Int <info>get</info>() = 1
|
<info>abstract</info> val x2 : Int <info>get</info>() = 1
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
class PrivateVal {
|
class PrivateVal() {
|
||||||
private val prop = 0;
|
private val prop = 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
class PrivateVar {
|
class PrivateVar() {
|
||||||
private var x = 0;
|
private var x = 0;
|
||||||
|
|
||||||
fun setValueOfX(aValue: Int) { x = aValue }
|
fun setValueOfX(aValue: Int) { x = aValue }
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ public class PropertyGenTest extends CodegenTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void testPublicVar() throws Exception {
|
public void testPublicVar() throws Exception {
|
||||||
loadText("class PublicVar { public var foo = 0; }");
|
loadText("class PublicVar() { public var foo = 0; }");
|
||||||
System.out.println(generateToText());
|
System.out.println(generateToText());
|
||||||
final Class aClass = loadImplementationClass(generateClassesInFile(), "PublicVar");
|
final Class aClass = loadImplementationClass(generateClassesInFile(), "PublicVar");
|
||||||
final Object instance = aClass.newInstance();
|
final Object instance = aClass.newInstance();
|
||||||
@@ -41,7 +41,7 @@ public class PropertyGenTest extends CodegenTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void testAccessorsInInterface() {
|
public void testAccessorsInInterface() {
|
||||||
loadText("class AccessorsInInterface { public var foo = 0; }");
|
loadText("class AccessorsInInterface() { public var foo = 0; }");
|
||||||
final Class aClass = loadClass("AccessorsInInterface", generateClassesInFile());
|
final Class aClass = loadClass("AccessorsInInterface", generateClassesInFile());
|
||||||
assertNotNull(findMethodByName(aClass, "getFoo"));
|
assertNotNull(findMethodByName(aClass, "getFoo"));
|
||||||
assertNotNull(findMethodByName(aClass, "setFoo"));
|
assertNotNull(findMethodByName(aClass, "setFoo"));
|
||||||
@@ -82,7 +82,7 @@ public class PropertyGenTest extends CodegenTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void testAccessorsWithoutBody() throws Exception {
|
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 Class aClass = loadImplementationClass(generateClassesInFile(), "AccessorsWithoutBody");
|
||||||
final Object instance = aClass.newInstance();
|
final Object instance = aClass.newInstance();
|
||||||
final Method getFoo = findMethodByName(aClass, "getFoo");
|
final Method getFoo = findMethodByName(aClass, "getFoo");
|
||||||
|
|||||||
Reference in New Issue
Block a user