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.types.expressions;
018
019import com.intellij.psi.PsiElement;
020import com.intellij.util.containers.Stack;
021import org.jetbrains.annotations.NotNull;
022import org.jetbrains.annotations.Nullable;
023import org.jetbrains.jet.lang.descriptors.*;
024import org.jetbrains.jet.lang.psi.*;
025import org.jetbrains.jet.lang.resolve.BindingContext;
026import org.jetbrains.jet.lang.resolve.BindingContextUtils;
027import org.jetbrains.jet.lang.resolve.DescriptorResolver;
028import org.jetbrains.jet.lang.resolve.name.LabelName;
029
030import java.util.Collection;
031import java.util.HashMap;
032import java.util.Iterator;
033import java.util.Map;
034
035import static org.jetbrains.jet.lang.diagnostics.Errors.LABEL_NAME_CLASH;
036import static org.jetbrains.jet.lang.diagnostics.Errors.UNRESOLVED_REFERENCE;
037import static org.jetbrains.jet.lang.resolve.BindingContext.LABEL_TARGET;
038import static org.jetbrains.jet.lang.resolve.BindingContext.REFERENCE_TARGET;
039
040public class LabelResolver {
041
042    private final Map<LabelName, Stack<JetElement>> labeledElements = new HashMap<LabelName, Stack<JetElement>>();
043
044    public LabelResolver() {}
045
046    public void enterLabeledElement(@NotNull LabelName labelName, @NotNull JetExpression labeledExpression) {
047        JetExpression cacheExpression = getCachingExpression(labeledExpression);
048        if (cacheExpression != null) {
049            Stack<JetElement> stack = labeledElements.get(labelName);
050            if (stack == null) {
051                stack = new Stack<JetElement>();
052                labeledElements.put(labelName, stack);
053            }
054            stack.push(cacheExpression);
055        }
056    }
057
058    public void exitLabeledElement(@NotNull JetExpression expression) {
059        JetExpression cacheExpression = getCachingExpression(expression);
060
061        // TODO : really suboptimal
062        for (Iterator<Map.Entry<LabelName,Stack<JetElement>>> mapIter = labeledElements.entrySet().iterator(); mapIter.hasNext(); ) {
063            Map.Entry<LabelName, Stack<JetElement>> entry = mapIter.next();
064            Stack<JetElement> stack = entry.getValue();
065            for (Iterator<JetElement> stackIter = stack.iterator(); stackIter.hasNext(); ) {
066                JetElement recorded = stackIter.next();
067                if (recorded == cacheExpression) {
068                    stackIter.remove();
069                }
070            }
071            if (stack.isEmpty()) {
072                mapIter.remove();
073            }
074        }
075    }
076
077    @NotNull
078    private JetExpression getCachingExpression(@NotNull JetExpression labeledExpression) {
079        JetExpression expression = JetPsiUtil.deparenthesizeWithNoTypeResolution(labeledExpression);
080        if (expression instanceof JetFunctionLiteralExpression) {
081            expression = ((JetFunctionLiteralExpression) expression).getFunctionLiteral();
082        }
083        return expression;
084    }
085
086    @Nullable
087    private JetElement resolveControlLabel(@NotNull LabelName labelName, @NotNull JetSimpleNameExpression labelExpression, boolean reportUnresolved, ExpressionTypingContext context) {
088        Collection<DeclarationDescriptor> declarationsByLabel = context.scope.getDeclarationsByLabel(labelName);
089        int size = declarationsByLabel.size();
090
091        if (size == 1) {
092            DeclarationDescriptor declarationDescriptor = declarationsByLabel.iterator().next();
093            JetElement element;
094            if (declarationDescriptor instanceof FunctionDescriptor || declarationDescriptor instanceof ClassDescriptor) {
095                element = (JetElement) BindingContextUtils.descriptorToDeclaration(context.trace.getBindingContext(), declarationDescriptor);
096            }
097            else {
098                throw new UnsupportedOperationException(declarationDescriptor.getClass().toString()); // TODO
099            }
100            context.trace.record(LABEL_TARGET, labelExpression, element);
101            return element;
102        }
103        else if (size == 0) {
104            return resolveNamedLabel(labelName, labelExpression, reportUnresolved, context);
105        }
106        BindingContextUtils.reportAmbiguousLabel(context.trace, labelExpression, declarationsByLabel);
107        return null;
108    }
109
110    @Nullable
111    public JetElement resolveLabel(JetLabelQualifiedExpression expression, ExpressionTypingContext context) {
112        JetSimpleNameExpression labelElement = expression.getTargetLabel();
113        if (labelElement != null) {
114            LabelName labelName = new LabelName(expression.getLabelName());
115            return resolveControlLabel(labelName, labelElement, true, context);
116        }
117        return null;
118    }
119
120    private JetElement resolveNamedLabel(@NotNull LabelName labelName, @NotNull JetSimpleNameExpression labelExpression, boolean reportUnresolved, ExpressionTypingContext context) {
121        Stack<JetElement> stack = labeledElements.get(labelName);
122        if (stack == null || stack.isEmpty()) {
123            if (reportUnresolved) {
124                context.trace.report(UNRESOLVED_REFERENCE.on(labelExpression, labelExpression));
125            }
126            return null;
127        }
128        else if (stack.size() > 1) {
129            context.trace.report(LABEL_NAME_CLASH.on(labelExpression));
130        }
131
132        JetElement result = stack.peek();
133        context.trace.record(LABEL_TARGET, labelExpression, result);
134        return result;
135    }
136
137    public LabeledReceiverResolutionResult resolveThisLabel(JetReferenceExpression thisReference, JetSimpleNameExpression targetLabel,
138            ExpressionTypingContext context, LabelName labelName) {
139        Collection<DeclarationDescriptor> declarationsByLabel = context.scope.getDeclarationsByLabel(labelName);
140        int size = declarationsByLabel.size();
141        assert targetLabel != null;
142        if (size == 1) {
143            DeclarationDescriptor declarationDescriptor = declarationsByLabel.iterator().next();
144            ReceiverParameterDescriptor thisReceiver;
145            if (declarationDescriptor instanceof ClassDescriptor) {
146                ClassDescriptor classDescriptor = (ClassDescriptor) declarationDescriptor;
147                thisReceiver = classDescriptor.getThisAsReceiverParameter();
148            }
149            else if (declarationDescriptor instanceof FunctionDescriptor) {
150                FunctionDescriptor functionDescriptor = (FunctionDescriptor) declarationDescriptor;
151                thisReceiver = functionDescriptor.getReceiverParameter();
152            }
153            else if (declarationDescriptor instanceof PropertyDescriptor) {
154                PropertyDescriptor propertyDescriptor = (PropertyDescriptor) declarationDescriptor;
155                thisReceiver = propertyDescriptor.getReceiverParameter();
156            }
157            else {
158                throw new UnsupportedOperationException("Unsupported descriptor: " + declarationDescriptor); // TODO
159            }
160            PsiElement element = BindingContextUtils.descriptorToDeclaration(context.trace.getBindingContext(), declarationDescriptor);
161            assert element != null : "No PSI element for descriptor: " + declarationDescriptor;
162            context.trace.record(LABEL_TARGET, targetLabel, element);
163            context.trace.record(REFERENCE_TARGET, thisReference, declarationDescriptor);
164
165            if (declarationDescriptor instanceof ClassDescriptor) {
166                ClassDescriptor classDescriptor = (ClassDescriptor) declarationDescriptor;
167                if (!DescriptorResolver.checkHasOuterClassInstance(context.scope, context.trace, targetLabel, classDescriptor)) {
168                    return LabeledReceiverResolutionResult.labelResolutionFailed();
169                }
170            }
171
172            return LabeledReceiverResolutionResult.labelResolutionSuccess(thisReceiver);
173        }
174        else if (size == 0) {
175            JetElement element = resolveNamedLabel(labelName, targetLabel, false, context);
176            if (element instanceof JetFunctionLiteral) {
177                DeclarationDescriptor declarationDescriptor =
178                        context.trace.getBindingContext().get(BindingContext.DECLARATION_TO_DESCRIPTOR, element);
179                if (declarationDescriptor instanceof FunctionDescriptor) {
180                    ReceiverParameterDescriptor thisReceiver = ((FunctionDescriptor) declarationDescriptor).getReceiverParameter();
181                    if (thisReceiver != null) {
182                        context.trace.record(LABEL_TARGET, targetLabel, element);
183                        context.trace.record(REFERENCE_TARGET, thisReference, declarationDescriptor);
184                    }
185                    return LabeledReceiverResolutionResult.labelResolutionSuccess(thisReceiver);
186                }
187                else {
188                    context.trace.report(UNRESOLVED_REFERENCE.on(targetLabel, targetLabel));
189                }
190            }
191            else {
192                context.trace.report(UNRESOLVED_REFERENCE.on(targetLabel, targetLabel));
193            }
194        }
195        else {
196            BindingContextUtils.reportAmbiguousLabel(context.trace, targetLabel, declarationsByLabel);
197        }
198        return LabeledReceiverResolutionResult.labelResolutionFailed();
199    }
200
201    public static final class LabeledReceiverResolutionResult {
202        public static LabeledReceiverResolutionResult labelResolutionSuccess(@Nullable ReceiverParameterDescriptor receiverParameterDescriptor) {
203            if (receiverParameterDescriptor == null) {
204                return new LabeledReceiverResolutionResult(Code.NO_THIS, null);
205            }
206            return new LabeledReceiverResolutionResult(Code.SUCCESS, receiverParameterDescriptor);
207        }
208
209        public static LabeledReceiverResolutionResult labelResolutionFailed() {
210            return new LabeledReceiverResolutionResult(Code.LABEL_RESOLUTION_ERROR, null);
211        }
212
213        public enum Code {
214            LABEL_RESOLUTION_ERROR,
215            NO_THIS,
216            SUCCESS
217        }
218
219        private final Code code;
220        private final ReceiverParameterDescriptor receiverParameterDescriptor;
221
222        private LabeledReceiverResolutionResult(
223                Code code,
224                ReceiverParameterDescriptor receiverParameterDescriptor
225        ) {
226            this.code = code;
227            this.receiverParameterDescriptor = receiverParameterDescriptor;
228        }
229
230        public Code getCode() {
231            return code;
232        }
233
234        public boolean success() {
235            return code == Code.SUCCESS;
236        }
237
238        public ReceiverParameterDescriptor getReceiverParameterDescriptor() {
239            assert success() : "Don't try to obtain the receiver when resolution failed with " + code;
240            return receiverParameterDescriptor;
241        }
242    }
243}