001/*
002 * Copyright 2010-2013 JetBrains s.r.o.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.jetbrains.jet.lang.resolve;
018
019import com.google.common.collect.Sets;
020import com.intellij.lang.ASTNode;
021import org.jetbrains.annotations.NotNull;
022import org.jetbrains.jet.lang.descriptors.*;
023import org.jetbrains.jet.lang.descriptors.impl.MutableClassDescriptor;
024import org.jetbrains.jet.lang.diagnostics.Errors;
025import org.jetbrains.jet.lang.psi.*;
026import org.jetbrains.jet.lang.types.DeferredType;
027import org.jetbrains.jet.lang.types.JetType;
028import org.jetbrains.jet.lexer.JetKeywordToken;
029import org.jetbrains.jet.lexer.JetTokens;
030
031import javax.inject.Inject;
032import java.util.List;
033import java.util.Map;
034
035import static org.jetbrains.jet.lang.diagnostics.Errors.*;
036import static org.jetbrains.jet.lang.resolve.BindingContext.TYPE;
037
038public class DeclarationsChecker {
039    @NotNull
040    private BindingTrace trace;
041    @NotNull
042    private ModifiersChecker modifiersChecker;
043
044    @Inject
045    public void setTrace(@NotNull BindingTrace trace) {
046        this.trace = trace;
047        this.modifiersChecker = new ModifiersChecker(trace);
048    }
049
050    public void process(@NotNull BodiesResolveContext bodiesResolveContext) {
051        Map<JetClass, MutableClassDescriptor> classes = bodiesResolveContext.getClasses();
052        for (Map.Entry<JetClass, MutableClassDescriptor> entry : classes.entrySet()) {
053            JetClass aClass = entry.getKey();
054            MutableClassDescriptor classDescriptor = entry.getValue();
055            if (!bodiesResolveContext.completeAnalysisNeeded(aClass)) continue;
056
057            checkClass(aClass, classDescriptor);
058            modifiersChecker.checkModifiersForDeclaration(aClass, classDescriptor);
059        }
060
061        Map<JetObjectDeclaration, MutableClassDescriptor> objects = bodiesResolveContext.getObjects();
062        for (Map.Entry<JetObjectDeclaration, MutableClassDescriptor> entry : objects.entrySet()) {
063            JetObjectDeclaration objectDeclaration = entry.getKey();
064            MutableClassDescriptor objectDescriptor = entry.getValue();
065
066            if (!bodiesResolveContext.completeAnalysisNeeded(objectDeclaration)) continue;
067            checkObject(objectDeclaration);
068            modifiersChecker.checkModifiersForDeclaration(objectDeclaration, objectDescriptor);
069        }
070
071        Map<JetNamedFunction, SimpleFunctionDescriptor> functions = bodiesResolveContext.getFunctions();
072        for (Map.Entry<JetNamedFunction, SimpleFunctionDescriptor> entry : functions.entrySet()) {
073            JetNamedFunction function = entry.getKey();
074            SimpleFunctionDescriptor functionDescriptor = entry.getValue();
075
076            if (!bodiesResolveContext.completeAnalysisNeeded(function)) continue;
077            checkFunction(function, functionDescriptor);
078            modifiersChecker.checkModifiersForDeclaration(function, functionDescriptor);
079        }
080
081        Map<JetProperty, PropertyDescriptor> properties = bodiesResolveContext.getProperties();
082        for (Map.Entry<JetProperty, PropertyDescriptor> entry : properties.entrySet()) {
083            JetProperty property = entry.getKey();
084            PropertyDescriptor propertyDescriptor = entry.getValue();
085
086            if (!bodiesResolveContext.completeAnalysisNeeded(property)) continue;
087            checkProperty(property, propertyDescriptor);
088            modifiersChecker.checkModifiersForDeclaration(property, propertyDescriptor);
089        }
090
091    }
092
093    private void reportErrorIfHasEnumModifier(JetModifierListOwner declaration) {
094        if (declaration.hasModifier(JetTokens.ENUM_KEYWORD)) {
095            trace.report(ILLEGAL_ENUM_ANNOTATION.on(declaration));
096        }
097    }
098    
099    private void checkObject(JetObjectDeclaration declaration) {
100        reportErrorIfHasEnumModifier(declaration);
101    }
102
103    private void checkClass(JetClass aClass, MutableClassDescriptor classDescriptor) {
104        checkOpenMembers(classDescriptor);
105        if (aClass.isTrait()) {
106            checkTraitModifiers(aClass);
107        }
108        else if (classDescriptor.getKind() == ClassKind.ENUM_CLASS) {
109            checkEnumModifiers(aClass);
110        }
111        else if (classDescriptor.getKind() == ClassKind.ENUM_ENTRY) {
112            checkEnumEntry(aClass, classDescriptor);
113        }
114    }
115
116    private void checkTraitModifiers(JetClass aClass) {
117        reportErrorIfHasEnumModifier(aClass);
118        JetModifierList modifierList = aClass.getModifierList();
119        if (modifierList == null) return;
120        if (modifierList.hasModifier(JetTokens.FINAL_KEYWORD)) {
121            trace.report(Errors.TRAIT_CAN_NOT_BE_FINAL.on(modifierList.getModifierNode(JetTokens.FINAL_KEYWORD).getPsi()));
122        }
123        if (modifierList.hasModifier(JetTokens.ABSTRACT_KEYWORD)) {
124            trace.report(Errors.ABSTRACT_MODIFIER_IN_TRAIT.on(aClass));
125        }
126        if (modifierList.hasModifier(JetTokens.OPEN_KEYWORD)) {
127            trace.report(Errors.OPEN_MODIFIER_IN_TRAIT.on(aClass));
128        }
129    }
130
131
132    private void checkOpenMembers(MutableClassDescriptor classDescriptor) {
133        for (CallableMemberDescriptor memberDescriptor : classDescriptor.getDeclaredCallableMembers()) {
134            if (memberDescriptor.getKind() != CallableMemberDescriptor.Kind.DECLARATION) continue;
135            JetNamedDeclaration member = (JetNamedDeclaration) BindingContextUtils.descriptorToDeclaration(trace.getBindingContext(), memberDescriptor);
136            if (member != null && classDescriptor.getModality() == Modality.FINAL && member.hasModifier(JetTokens.OPEN_KEYWORD)) {
137                trace.report(NON_FINAL_MEMBER_IN_FINAL_CLASS.on(member));
138            }
139        }
140    }
141
142    private void checkProperty(JetProperty property, PropertyDescriptor propertyDescriptor) {
143        reportErrorIfHasEnumModifier(property);
144        DeclarationDescriptor containingDeclaration = propertyDescriptor.getContainingDeclaration();
145        if (containingDeclaration instanceof ClassDescriptor) {
146            checkPropertyAbstractness(property, propertyDescriptor, (ClassDescriptor) containingDeclaration);
147        }
148        else {
149            modifiersChecker.checkIllegalModalityModifiers(property);
150        }
151        checkPropertyInitializer(property, propertyDescriptor);
152        checkAccessors(property, propertyDescriptor);
153        checkDeclaredTypeInPublicMember(property, propertyDescriptor);
154    }
155
156    private void checkDeclaredTypeInPublicMember(JetNamedDeclaration member, CallableMemberDescriptor memberDescriptor) {
157        boolean hasDeferredType;
158        if (member instanceof JetProperty) {
159            hasDeferredType = ((JetProperty) member).getTypeRef() == null && DescriptorResolver.hasBody((JetProperty) member);
160        }
161        else {
162            assert member instanceof JetFunction;
163            JetFunction function = (JetFunction) member;
164            hasDeferredType = function.getReturnTypeRef() == null && function.getBodyExpression() != null && !function.hasBlockBody();
165        }
166        if ((memberDescriptor.getVisibility().isPublicAPI()) && memberDescriptor.getOverriddenDescriptors().size() == 0 && hasDeferredType) {
167            trace.report(PUBLIC_MEMBER_SHOULD_SPECIFY_TYPE.on(member));
168        }
169    }
170
171    private void checkPropertyAbstractness(
172            @NotNull JetProperty property,
173            @NotNull PropertyDescriptor propertyDescriptor,
174            @NotNull ClassDescriptor classDescriptor
175    ) {
176        JetPropertyAccessor getter = property.getGetter();
177        JetPropertyAccessor setter = property.getSetter();
178        JetModifierList modifierList = property.getModifierList();
179        ASTNode abstractNode = modifierList != null ? modifierList.getModifierNode(JetTokens.ABSTRACT_KEYWORD) : null;
180
181        if (abstractNode != null) { //has abstract modifier
182            if (!(classDescriptor.getModality() == Modality.ABSTRACT) && classDescriptor.getKind() != ClassKind.ENUM_CLASS) {
183                String name = property.getName();
184                trace.report(ABSTRACT_PROPERTY_IN_NON_ABSTRACT_CLASS.on(property, name != null ? name : "", classDescriptor));
185                return;
186            }
187            if (classDescriptor.getKind() == ClassKind.TRAIT) {
188                trace.report(ABSTRACT_MODIFIER_IN_TRAIT.on(property));
189            }
190        }
191
192        if (propertyDescriptor.getModality() == Modality.ABSTRACT) {
193            JetType returnType = propertyDescriptor.getReturnType();
194            if (returnType instanceof DeferredType) {
195                returnType = ((DeferredType) returnType).getActualType();
196            }
197
198            JetExpression initializer = property.getInitializer();
199            if (initializer != null) {
200                trace.report(ABSTRACT_PROPERTY_WITH_INITIALIZER.on(initializer));
201            }
202            JetPropertyDelegate delegate = property.getDelegate();
203            if (delegate != null) {
204                trace.report(ABSTRACT_DELEGATED_PROPERTY.on(delegate));
205            }
206            if (getter != null && getter.getBodyExpression() != null) {
207                trace.report(ABSTRACT_PROPERTY_WITH_GETTER.on(getter));
208            }
209            if (setter != null && setter.getBodyExpression() != null) {
210                trace.report(ABSTRACT_PROPERTY_WITH_SETTER.on(setter));
211            }
212        }
213    }
214
215    private void checkPropertyInitializer(
216            @NotNull JetProperty property,
217            @NotNull PropertyDescriptor propertyDescriptor
218    ) {
219        JetPropertyAccessor getter = property.getGetter();
220        JetPropertyAccessor setter = property.getSetter();
221        boolean hasAccessorImplementation = (getter != null && getter.getBodyExpression() != null) ||
222                                            (setter != null && setter.getBodyExpression() != null);
223
224        if (propertyDescriptor.getModality() == Modality.ABSTRACT) {
225            if (property.getDelegateExpressionOrInitializer() == null && property.getTypeRef() == null) {
226                trace.report(PROPERTY_WITH_NO_TYPE_NO_INITIALIZER.on(property));
227            }
228            return;
229        }
230        DeclarationDescriptor containingDeclaration = propertyDescriptor.getContainingDeclaration();
231        boolean inTrait = containingDeclaration instanceof ClassDescriptor && ((ClassDescriptor)containingDeclaration).getKind() == ClassKind.TRAIT;
232        JetExpression initializer = property.getInitializer();
233        JetPropertyDelegate delegate = property.getDelegate();
234        boolean backingFieldRequired = trace.getBindingContext().get(BindingContext.BACKING_FIELD_REQUIRED, propertyDescriptor);
235
236        if (inTrait && backingFieldRequired && hasAccessorImplementation) {
237            trace.report(BACKING_FIELD_IN_TRAIT.on(property));
238        }
239        if (initializer == null && delegate == null) {
240            boolean error = false;
241            if (backingFieldRequired && !inTrait && !trace.getBindingContext().get(BindingContext.IS_INITIALIZED, propertyDescriptor)) {
242                if (!(containingDeclaration instanceof ClassDescriptor) || hasAccessorImplementation) {
243                    error = true;
244                    trace.report(MUST_BE_INITIALIZED.on(property));
245                }
246                else {
247                    error = true;
248                    trace.report(MUST_BE_INITIALIZED_OR_BE_ABSTRACT.on(property));
249                }
250            }
251            if (!error && property.getTypeRef() == null) {
252                trace.report(PROPERTY_WITH_NO_TYPE_NO_INITIALIZER.on(property));
253            }
254            if (inTrait && property.hasModifier(JetTokens.FINAL_KEYWORD) && backingFieldRequired) {
255                trace.report(FINAL_PROPERTY_IN_TRAIT.on(property));
256            }
257            return;
258        }
259        if (inTrait) {
260            if (delegate != null) {
261                trace.report(DELEGATED_PROPERTY_IN_TRAIT.on(delegate));
262            }
263            else {
264                trace.report(PROPERTY_INITIALIZER_IN_TRAIT.on(initializer));
265            }
266        }
267        else if (!backingFieldRequired && delegate == null) {
268            trace.report(PROPERTY_INITIALIZER_NO_BACKING_FIELD.on(initializer));
269        }
270    }
271
272    protected void checkFunction(JetNamedFunction function, SimpleFunctionDescriptor functionDescriptor) {
273        reportErrorIfHasEnumModifier(function);
274        DeclarationDescriptor containingDescriptor = functionDescriptor.getContainingDeclaration();
275        boolean hasAbstractModifier = function.hasModifier(JetTokens.ABSTRACT_KEYWORD);
276        checkDeclaredTypeInPublicMember(function, functionDescriptor);
277        if (containingDescriptor instanceof ClassDescriptor) {
278            ClassDescriptor classDescriptor = (ClassDescriptor) containingDescriptor;
279            boolean inTrait = classDescriptor.getKind() == ClassKind.TRAIT;
280            boolean inEnum = classDescriptor.getKind() == ClassKind.ENUM_CLASS;
281            boolean inAbstractClass = classDescriptor.getModality() == Modality.ABSTRACT;
282            if (hasAbstractModifier && !inAbstractClass && !inEnum) {
283                trace.report(ABSTRACT_FUNCTION_IN_NON_ABSTRACT_CLASS.on(function, functionDescriptor.getName().asString(), classDescriptor));
284            }
285            if (hasAbstractModifier && inTrait) {
286                trace.report(ABSTRACT_MODIFIER_IN_TRAIT.on(function));
287            }
288            boolean hasBody = function.getBodyExpression() != null;
289            if (hasBody && hasAbstractModifier) {
290                trace.report(ABSTRACT_FUNCTION_WITH_BODY.on(function, functionDescriptor));
291            }
292            if (!hasBody && function.hasModifier(JetTokens.FINAL_KEYWORD) && inTrait) {
293                trace.report(FINAL_FUNCTION_WITH_NO_BODY.on(function, functionDescriptor));
294            }
295            if (!hasBody && !hasAbstractModifier && !inTrait) {
296                trace.report(NON_ABSTRACT_FUNCTION_WITH_NO_BODY.on(function, functionDescriptor));
297            }
298            return;
299        }
300        modifiersChecker.checkIllegalModalityModifiers(function);
301        if (function.getBodyExpression() == null && !hasAbstractModifier) {
302            trace.report(NON_MEMBER_FUNCTION_NO_BODY.on(function, functionDescriptor));
303        }
304    }
305
306    private void checkAccessors(@NotNull JetProperty property, @NotNull PropertyDescriptor propertyDescriptor) {
307        for (JetPropertyAccessor accessor : property.getAccessors()) {
308            modifiersChecker.checkIllegalModalityModifiers(accessor);
309        }
310        JetPropertyAccessor getter = property.getGetter();
311        PropertyGetterDescriptor getterDescriptor = propertyDescriptor.getGetter();
312        JetModifierList getterModifierList = getter != null ? getter.getModifierList() : null;
313        if (getterModifierList != null && getterDescriptor != null) {
314            Map<JetKeywordToken, ASTNode> nodes = ModifiersChecker.getNodesCorrespondingToModifiers(getterModifierList, Sets
315                    .newHashSet(JetTokens.PUBLIC_KEYWORD, JetTokens.PROTECTED_KEYWORD, JetTokens.PRIVATE_KEYWORD,
316                                JetTokens.INTERNAL_KEYWORD));
317            if (getterDescriptor.getVisibility() != propertyDescriptor.getVisibility()) {
318                for (ASTNode node : nodes.values()) {
319                    trace.report(Errors.GETTER_VISIBILITY_DIFFERS_FROM_PROPERTY_VISIBILITY.on(node.getPsi()));
320                }
321            }
322            else {
323                for (ASTNode node : nodes.values()) {
324                    trace.report(Errors.REDUNDANT_MODIFIER_IN_GETTER.on(node.getPsi()));
325                }
326            }
327        }
328    }
329
330    private void checkEnumModifiers(JetClass aClass) {
331        if (aClass.hasModifier(JetTokens.OPEN_KEYWORD)) {
332            trace.report(OPEN_MODIFIER_IN_ENUM.on(aClass));
333        }
334    }
335
336    private void checkEnumEntry(JetClass aClass, ClassDescriptor classDescriptor) {
337        DeclarationDescriptor declaration = classDescriptor.getContainingDeclaration().getContainingDeclaration();
338        assert declaration instanceof ClassDescriptor;
339        ClassDescriptor enumClass = (ClassDescriptor) declaration;
340        assert enumClass.getKind() == ClassKind.ENUM_CLASS;
341
342        List<JetDelegationSpecifier> delegationSpecifiers = aClass.getDelegationSpecifiers();
343        ConstructorDescriptor constructor = enumClass.getUnsubstitutedPrimaryConstructor();
344        assert constructor != null;
345        if (!constructor.getValueParameters().isEmpty() && delegationSpecifiers.isEmpty()) {
346            trace.report(ENUM_ENTRY_SHOULD_BE_INITIALIZED.on(aClass, enumClass));
347        }
348
349        for (JetDelegationSpecifier delegationSpecifier : delegationSpecifiers) {
350            JetTypeReference typeReference = delegationSpecifier.getTypeReference();
351            if (typeReference != null) {
352                JetType type = trace.getBindingContext().get(TYPE, typeReference);
353                if (type != null) {
354                    JetType enumType = enumClass.getDefaultType();
355                    if (!type.getConstructor().equals(enumType.getConstructor())) {
356                        trace.report(ENUM_ENTRY_ILLEGAL_TYPE.on(typeReference, enumClass));
357                    }
358                }
359            }
360        }
361    }
362}