From cec836900b9942601a014bc4c9524991dc4ca147 Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Wed, 7 Sep 2011 11:34:21 +0400 Subject: [PATCH] KT-284 Implement static checks for traits (in progress) --- .../descriptors/MutableClassDescriptor.java | 2 +- .../lang/resolve/ClassDescriptorResolver.java | 6 +- .../jet/lang/resolve/TopDownAnalyzer.java | 174 +++++++++++------- .../TopDownAnalyzerForStandardLibrary.java | 2 +- .../util/slicedmap/BasicWritableSlice.java | 15 +- .../jet/util/slicedmap/DelegatingSlice.java | 40 ++++ .../jet/util/slicedmap/ReadOnlySlice.java | 4 + 7 files changed, 175 insertions(+), 68 deletions(-) create mode 100644 idea/src/org/jetbrains/jet/util/slicedmap/DelegatingSlice.java diff --git a/idea/src/org/jetbrains/jet/lang/descriptors/MutableClassDescriptor.java b/idea/src/org/jetbrains/jet/lang/descriptors/MutableClassDescriptor.java index bd99d0e794c..4c0eaa3e366 100644 --- a/idea/src/org/jetbrains/jet/lang/descriptors/MutableClassDescriptor.java +++ b/idea/src/org/jetbrains/jet/lang/descriptors/MutableClassDescriptor.java @@ -145,7 +145,7 @@ public class MutableClassDescriptor extends MutableDeclarationDescriptor impleme @Override public boolean isAbstract() { - return isAbstract; + return isAbstract || trait; } public void setAbstract(boolean isAbstract) { diff --git a/idea/src/org/jetbrains/jet/lang/resolve/ClassDescriptorResolver.java b/idea/src/org/jetbrains/jet/lang/resolve/ClassDescriptorResolver.java index bfeb02bd50f..155f6449a1f 100644 --- a/idea/src/org/jetbrains/jet/lang/resolve/ClassDescriptorResolver.java +++ b/idea/src/org/jetbrains/jet/lang/resolve/ClassDescriptorResolver.java @@ -166,15 +166,15 @@ public class ClassDescriptorResolver { trace.getErrorHandler().genericError(((JetEnumEntry) jetClass).getNameIdentifier().getNode(), "generic arguments of the base type must be specified"); } } - Collection superclasses = delegationSpecifiers.isEmpty() + Collection supertypes = delegationSpecifiers.isEmpty() ? Collections.singleton(defaultSupertype) : resolveDelegationSpecifiers( descriptor.getScopeForSupertypeResolution(), delegationSpecifiers, typeResolverNotCheckingBounds); - for (JetType superclass : superclasses) { - descriptor.addSupertype(superclass); + for (JetType supertype : supertypes) { + descriptor.addSupertype(supertype); } } diff --git a/idea/src/org/jetbrains/jet/lang/resolve/TopDownAnalyzer.java b/idea/src/org/jetbrains/jet/lang/resolve/TopDownAnalyzer.java index d8f9234e2a5..4788df99411 100644 --- a/idea/src/org/jetbrains/jet/lang/resolve/TopDownAnalyzer.java +++ b/idea/src/org/jetbrains/jet/lang/resolve/TopDownAnalyzer.java @@ -2,6 +2,7 @@ package org.jetbrains.jet.lang.resolve; import com.google.common.collect.Maps; import com.google.common.collect.Sets; +import com.intellij.lang.ASTNode; import com.intellij.psi.PsiElement; import com.intellij.psi.util.PsiTreeUtil; import org.jetbrains.annotations.NotNull; @@ -119,7 +120,7 @@ public class TopDownAnalyzer { createTypeConstructors(); // create type constructors for classes and generic parameters resolveTypesInClassHeaders(); // Generic bounds and types in supertype lists (no expressions or constructor resolution) - checkGenericBoundsInClassHeaders(); // For the types resolved so far + checkTypesInClassHeaders(); // Generic bounds and supertype lists resolveConstructorHeaders(); @@ -346,7 +347,7 @@ public class TopDownAnalyzer { } } - private void checkGenericBoundsInClassHeaders() { + private void checkTypesInClassHeaders() { for (Map.Entry entry : classes.entrySet()) { JetClass jetClass = entry.getKey(); @@ -354,7 +355,9 @@ public class TopDownAnalyzer { JetTypeReference typeReference = delegationSpecifier.getTypeReference(); if (typeReference != null) { JetType type = trace.getBindingContext().get(BindingContext.TYPE, typeReference); - classDescriptorResolver.checkBounds(typeReference, type); + if (type != null) { + classDescriptorResolver.checkBounds(typeReference, type); + } } } @@ -479,6 +482,10 @@ public class TopDownAnalyzer { private void processPrimaryConstructor(MutableClassDescriptor classDescriptor, JetClass klass) { if (!klass.hasPrimaryConstructor()) return; + if (classDescriptor.isTrait()) { + trace.getErrorHandler().genericError(klass.getPrimaryConstructorParameterList().getNode(), "A trait may not have a constructor"); + } + // TODO : not all the parameters are real properties JetScope memberScope = classDescriptor.getScopeForSupertypeResolution(); ConstructorDescriptor constructorDescriptor = classDescriptorResolver.resolvePrimaryConstructorDescriptor(memberScope, classDescriptor, klass); @@ -497,6 +504,9 @@ public class TopDownAnalyzer { } private void processSecondaryConstructor(MutableClassDescriptor classDescriptor, JetConstructor constructor) { + if (classDescriptor.isTrait()) { + trace.getErrorHandler().genericError(constructor.getNameNode(), "A trait may not have a constructor"); + } ConstructorDescriptor constructorDescriptor = classDescriptorResolver.resolveSecondaryConstructorDescriptor( classDescriptor.getScopeForMemberResolution(), classDescriptor, @@ -591,65 +601,99 @@ public class TopDownAnalyzer { : getInnerScopeForConstructor(primaryConstructor, descriptor.getScopeForMemberResolution(), true); final JetTypeInferrer.Services typeInferrer = semanticServices.getTypeInferrerServices(traceForConstructors, JetFlowInformationProvider.NONE); // TODO : flow - for (JetDelegationSpecifier delegationSpecifier : jetClass.getDelegationSpecifiers()) { - delegationSpecifier.accept(new JetVisitorVoid() { - @Override - public void visitDelegationByExpressionSpecifier(JetDelegatorByExpressionSpecifier specifier) { - JetExpression delegateExpression = specifier.getDelegateExpression(); - if (delegateExpression != null) { - JetScope scope = scopeForConstructor == null ? descriptor.getScopeForMemberResolution() : scopeForConstructor; - JetType type = typeInferrer.getType(scope, delegateExpression, NO_EXPECTED_TYPE); - JetType supertype = trace.getBindingContext().get(BindingContext.TYPE, specifier.getTypeReference()); - if (type != null && !semanticServices.getTypeChecker().isSubtypeOf(type, supertype)) { // TODO : Convertible? - trace.getErrorHandler().typeMismatch(delegateExpression, supertype, type); - } - } + JetVisitorVoid visitor = new JetVisitorVoid() { + private boolean superclassAppeared = false; + + @Override + public void visitDelegationByExpressionSpecifier(JetDelegatorByExpressionSpecifier specifier) { + if (descriptor.isTrait()) { + trace.getErrorHandler().genericError(specifier.getNode(), "Traits can not use delegation"); } - - @Override - public void visitDelegationToSuperCallSpecifier(JetDelegatorToSuperCall call) { - JetTypeReference typeReference = call.getTypeReference(); - if (typeReference != null) { - if (descriptor.getUnsubstitutedPrimaryConstructor() != null) { - typeInferrer.getCallResolver().resolveCall(trace, scopeForConstructor, null, call, NO_EXPECTED_TYPE); - } - else { - JetValueArgumentList valueArgumentList = call.getValueArgumentList(); - assert valueArgumentList != null; - trace.getErrorHandler().genericError(valueArgumentList.getNode(), - "Class " + JetPsiUtil.safeName(jetClass.getName()) + " must have a constructor in order to be able to initialize supertypes"); - } - } - } - - @Override - public void visitDelegationToSuperClassSpecifier(JetDelegatorToSuperClass specifier) { + JetExpression delegateExpression = specifier.getDelegateExpression(); + if (delegateExpression != null) { + JetScope scope = scopeForConstructor == null + ? descriptor.getScopeForMemberResolution() + : scopeForConstructor; + JetType type = typeInferrer.getType(scope, delegateExpression, NO_EXPECTED_TYPE); JetType supertype = trace.getBindingContext().get(BindingContext.TYPE, specifier.getTypeReference()); - if (supertype != null) { - DeclarationDescriptor declarationDescriptor = supertype.getConstructor().getDeclarationDescriptor(); - if (declarationDescriptor instanceof ClassDescriptor) { - ClassDescriptor classDescriptor = (ClassDescriptor) declarationDescriptor; - if (classDescriptor.hasConstructors() && !ErrorUtils.isError(classDescriptor.getTypeConstructor())) { + if (type != null && supertype != null && !semanticServices.getTypeChecker().isSubtypeOf(type, supertype)) { + trace.getErrorHandler().typeMismatch(delegateExpression, supertype, type); + } + } + } + + @Override + public void visitDelegationToSuperCallSpecifier(JetDelegatorToSuperCall call) { + if (descriptor.isTrait()) { + JetValueArgumentList valueArgumentList = call.getValueArgumentList(); + ASTNode node = valueArgumentList == null ? call.getNode() : valueArgumentList.getNode(); + trace.getErrorHandler().genericError(node, "Traits can not initialize supertypes"); + } + JetTypeReference typeReference = call.getTypeReference(); + if (typeReference != null) { + if (descriptor.getUnsubstitutedPrimaryConstructor() != null) { + JetType type = typeInferrer.getCallResolver().resolveCall(trace, scopeForConstructor, null, call, NO_EXPECTED_TYPE); + if (type != null) { + DeclarationDescriptor declarationDescriptor = type.getConstructor().getDeclarationDescriptor(); + if (declarationDescriptor instanceof ClassDescriptor) { + ClassDescriptor classDescriptor = (ClassDescriptor) declarationDescriptor; + if (classDescriptor.isTrait()) { + trace.getErrorHandler().genericError(call.getValueArgumentList().getNode(), "A trait may not have a constructor"); + } + } + } + } + else { + JetValueArgumentList valueArgumentList = call.getValueArgumentList(); + assert valueArgumentList != null; + trace.getErrorHandler().genericError(valueArgumentList.getNode(), + "Class " + JetPsiUtil.safeName(jetClass.getName()) + " must have a constructor in order to be able to initialize supertypes"); + } + } + } + + @Override + public void visitDelegationToSuperClassSpecifier(JetDelegatorToSuperClass specifier) { + JetType supertype = trace.getBindingContext().get(BindingContext.TYPE, specifier.getTypeReference()); + if (supertype != null) { + DeclarationDescriptor declarationDescriptor = supertype.getConstructor().getDeclarationDescriptor(); + if (declarationDescriptor instanceof ClassDescriptor) { + ClassDescriptor classDescriptor = (ClassDescriptor) declarationDescriptor; + if (!descriptor.isTrait()) { + if (classDescriptor.hasConstructors() && !ErrorUtils.isError(classDescriptor.getTypeConstructor()) && !classDescriptor.isTrait()) { trace.getErrorHandler().genericError(specifier.getNode(), "This type has a constructor, and thus must be initialized here"); } } else { - trace.getErrorHandler().genericError(specifier.getNode(), "Only classes may serve as supertypes"); + if (!classDescriptor.isTrait()) { + if (superclassAppeared) { + trace.getErrorHandler().genericError(specifier.getNode(), "A trait may extend only one class"); + } + else { + superclassAppeared = true; + } + } } - } - } + else { + trace.getErrorHandler().genericError(specifier.getNode(), "Only classes may serve as supertypes"); + } - @Override - public void visitDelegationToThisCall(JetDelegatorToThisCall thisCall) { - throw new IllegalStateException("This-calls should be prohibited by the parser"); } + } - @Override - public void visitJetElement(JetElement element) { - throw new UnsupportedOperationException(element.getText() + " : " + element); - } - }); + @Override + public void visitDelegationToThisCall(JetDelegatorToThisCall thisCall) { + throw new IllegalStateException("This-calls should be prohibited by the parser"); + } + + @Override + public void visitJetElement(JetElement element) { + throw new UnsupportedOperationException(element.getText() + " : " + element); + } + }; + for (JetDelegationSpecifier delegationSpecifier : jetClass.getDelegationSpecifiers()) { + delegationSpecifier.accept(visitor); } } @@ -815,7 +859,7 @@ public class TopDownAnalyzer { } resolvePropertyAccessors(property, propertyDescriptor, declaringScope); - checkPropertyCorrectness(property, propertyDescriptor, classDescriptor); + checkProperty(property, propertyDescriptor, classDescriptor); processed.add(property); } } @@ -834,7 +878,7 @@ public class TopDownAnalyzer { } resolvePropertyAccessors(property, propertyDescriptor, declaringScope); - checkPropertyCorrectness(property, propertyDescriptor, null); + checkProperty(property, propertyDescriptor, null); } } @@ -874,17 +918,19 @@ public class TopDownAnalyzer { // } } - protected void checkPropertyCorrectness(JetProperty property, PropertyDescriptor propertyDescriptor, @Nullable ClassDescriptor classDescriptor) { + protected void checkProperty(JetProperty property, PropertyDescriptor propertyDescriptor, @Nullable ClassDescriptor classDescriptor) { JetExpression initializer = property.getInitializer(); JetPropertyAccessor getter = property.getGetter(); JetPropertyAccessor setter = property.getSetter(); + PsiElement nameIdentifier = property.getNameIdentifier(); + ASTNode nameNode = nameIdentifier == null ? property.getNode() : nameIdentifier.getNode(); if (propertyDescriptor.getModifiers().isAbstract()) { if (classDescriptor == null) { trace.getErrorHandler().genericError(property.getModifierList().getModifierNode(JetTokens.ABSTRACT_KEYWORD), "Global property can not be abstract"); return; } - if (! classDescriptor.isAbstract()) { + if (!classDescriptor.isAbstract()) { trace.getErrorHandler().genericError(property.getModifierList().getModifierNode(JetTokens.ABSTRACT_KEYWORD), "Abstract property " + property.getName() + " in non-abstract class " + classDescriptor.getName()); return; @@ -901,14 +947,18 @@ public class TopDownAnalyzer { return; } boolean backingFieldRequired = trace.getBindingContext().get(BindingContext.BACKING_FIELD_REQUIRED, propertyDescriptor); - if (initializer != null && ! backingFieldRequired) { - trace.getErrorHandler().genericError(initializer.getNode(), "Initializer is not allowed here because this property has no backing field"); + if (backingFieldRequired) { + if (initializer == null && !trace.getBindingContext().get(BindingContext.IS_INITIALIZED, propertyDescriptor)) { + if (classDescriptor == null || (getter != null && getter.getBodyExpression() != null) || (setter != null && setter.getBodyExpression() != null)) { + trace.getErrorHandler().genericError(nameNode, "Property must be initialized"); + } else if (!classDescriptor.isTrait()) { + trace.getErrorHandler().genericError(nameNode, "Property must be initialized or be abstract"); + } + } } - if (initializer == null && backingFieldRequired && ! trace.getBindingContext().get(BindingContext.IS_INITIALIZED, propertyDescriptor)) { - if (classDescriptor == null || (getter != null && getter.getBodyExpression() != null) || (setter != null && setter.getBodyExpression() != null)) { - trace.getErrorHandler().genericError(property.getNameIdentifier().getNode(), "Property must be initialized"); - } else { - trace.getErrorHandler().genericError(property.getNameIdentifier().getNode(), "Property must be initialized or be abstract"); + else { + if (initializer != null) { + trace.getErrorHandler().genericError(initializer.getNode(), "Initializer is not allowed here because this property has no backing field"); } } } diff --git a/idea/src/org/jetbrains/jet/lang/resolve/TopDownAnalyzerForStandardLibrary.java b/idea/src/org/jetbrains/jet/lang/resolve/TopDownAnalyzerForStandardLibrary.java index 542662963d5..f0250ba86b9 100644 --- a/idea/src/org/jetbrains/jet/lang/resolve/TopDownAnalyzerForStandardLibrary.java +++ b/idea/src/org/jetbrains/jet/lang/resolve/TopDownAnalyzerForStandardLibrary.java @@ -23,7 +23,7 @@ public class TopDownAnalyzerForStandardLibrary extends TopDownAnalyzer { } @Override - protected void checkPropertyCorrectness(JetProperty property, PropertyDescriptor propertyDescriptor, ClassDescriptor classDescriptor) {} + protected void checkProperty(JetProperty property, PropertyDescriptor propertyDescriptor, ClassDescriptor classDescriptor) {} @Override diff --git a/idea/src/org/jetbrains/jet/util/slicedmap/BasicWritableSlice.java b/idea/src/org/jetbrains/jet/util/slicedmap/BasicWritableSlice.java index 30eb264bf7e..cef0c263261 100644 --- a/idea/src/org/jetbrains/jet/util/slicedmap/BasicWritableSlice.java +++ b/idea/src/org/jetbrains/jet/util/slicedmap/BasicWritableSlice.java @@ -37,14 +37,27 @@ public class BasicWritableSlice implements WritableSlice { return value; } - @Override public RewritePolicy getRewritePolicy() { return rewritePolicy; } + @Override public String toString() { return debugName; } + + @Override + public ReadOnlySlice makeRawValueVersion() { + return new DelegatingSlice(this) { + @Override + public V computeValue(SlicedMap map, K key, V value, boolean valueNotFound) { + if (valueNotFound) assert value == null; + return value; + } + }; + } + + } diff --git a/idea/src/org/jetbrains/jet/util/slicedmap/DelegatingSlice.java b/idea/src/org/jetbrains/jet/util/slicedmap/DelegatingSlice.java new file mode 100644 index 00000000000..cf415533340 --- /dev/null +++ b/idea/src/org/jetbrains/jet/util/slicedmap/DelegatingSlice.java @@ -0,0 +1,40 @@ +package org.jetbrains.jet.util.slicedmap; + +import org.jetbrains.annotations.NotNull; + +/** + * @author abreslav + */ +public class DelegatingSlice implements WritableSlice { + private final WritableSlice delegate; + + public DelegatingSlice(@NotNull WritableSlice delegate) { + this.delegate = delegate; + } + + public boolean check(K key, V value) { + return delegate.check(key, value); + } + + public void afterPut(MutableSlicedMap map, K key, V value) { + delegate.afterPut(map, key, value); + } + + @Override + public RewritePolicy getRewritePolicy() { + return delegate.getRewritePolicy(); + } + + public SlicedMapKey makeKey(K key) { + return delegate.makeKey(key); + } + + public V computeValue(SlicedMap map, K key, V value, boolean valueNotFound) { + return delegate.computeValue(map, key, value, valueNotFound); + } + + @Override + public ReadOnlySlice makeRawValueVersion() { + return delegate.makeRawValueVersion(); + } +} diff --git a/idea/src/org/jetbrains/jet/util/slicedmap/ReadOnlySlice.java b/idea/src/org/jetbrains/jet/util/slicedmap/ReadOnlySlice.java index fdbbac2cce8..696dd22440a 100644 --- a/idea/src/org/jetbrains/jet/util/slicedmap/ReadOnlySlice.java +++ b/idea/src/org/jetbrains/jet/util/slicedmap/ReadOnlySlice.java @@ -8,4 +8,8 @@ public interface ReadOnlySlice { V computeValue(SlicedMap map, K key, V value, boolean valueNotFound); + /** + * @return a slice that only retrieves the value from the storage and skips any computeValue() calls + */ + ReadOnlySlice makeRawValueVersion(); }