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.codegen.binding;
018
019import com.intellij.openapi.vfs.VirtualFile;
020import com.intellij.psi.PsiElement;
021import org.jetbrains.annotations.NotNull;
022import org.jetbrains.annotations.Nullable;
023import org.jetbrains.jet.lang.descriptors.*;
024import org.jetbrains.jet.lang.descriptors.annotations.AnnotationDescriptor;
025import org.jetbrains.jet.lang.descriptors.impl.ClassDescriptorImpl;
026import org.jetbrains.jet.lang.psi.*;
027import org.jetbrains.jet.lang.resolve.BindingContext;
028import org.jetbrains.jet.lang.resolve.BindingTrace;
029import org.jetbrains.jet.lang.resolve.java.JvmAbi;
030import org.jetbrains.jet.lang.resolve.java.JvmClassName;
031import org.jetbrains.jet.lang.resolve.java.descriptor.ClassDescriptorFromJvmBytecode;
032import org.jetbrains.jet.lang.resolve.name.FqName;
033import org.jetbrains.jet.lang.resolve.name.Name;
034import org.jetbrains.jet.lang.resolve.scopes.JetScope;
035import org.jetbrains.jet.lang.types.JetType;
036import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns;
037import org.jetbrains.jet.util.slicedmap.Slices;
038import org.jetbrains.jet.util.slicedmap.WritableSlice;
039
040import java.util.*;
041
042import static org.jetbrains.jet.codegen.CodegenUtil.isInterface;
043import static org.jetbrains.jet.lang.resolve.BindingContext.*;
044import static org.jetbrains.jet.lang.resolve.BindingContextUtils.descriptorToDeclaration;
045
046public class CodegenBinding {
047    public static final WritableSlice<ClassDescriptor, MutableClosure> CLOSURE = Slices.createSimpleSlice();
048
049    public static final WritableSlice<FunctionDescriptor, ClassDescriptor> CLASS_FOR_FUNCTION = Slices.createSimpleSlice();
050
051    public static final WritableSlice<ScriptDescriptor, ClassDescriptor> CLASS_FOR_SCRIPT = Slices.createSimpleSlice();
052
053    public static final WritableSlice<DeclarationDescriptor, JvmClassName> FQN = Slices.createSimpleSlice();
054
055    public static final WritableSlice<JvmClassName, Boolean> SCRIPT_NAMES = Slices.createSimpleSetSlice();
056
057    public static final WritableSlice<ClassDescriptor, Boolean> ENUM_ENTRY_CLASS_NEED_SUBCLASS = Slices.createSimpleSetSlice();
058
059    public static final WritableSlice<ClassDescriptor, Collection<ClassDescriptor>> INNER_CLASSES = Slices.createSimpleSlice();
060
061    public static final WritableSlice<JetExpression, ClassDescriptorFromJvmBytecode> SAM_VALUE = Slices.createSimpleSlice();
062
063    private CodegenBinding() {
064    }
065
066    public static void initTrace(BindingTrace bindingTrace, Collection<JetFile> files) {
067        CodegenAnnotatingVisitor visitor = new CodegenAnnotatingVisitor(bindingTrace);
068        for (JetFile file : allFilesInNamespaces(bindingTrace.getBindingContext(), files)) {
069            file.accept(visitor);
070        }
071    }
072
073    public static boolean enumEntryNeedSubclass(BindingContext bindingContext, JetEnumEntry enumEntry) {
074        return enumEntryNeedSubclass(bindingContext, bindingContext.get(CLASS, enumEntry));
075    }
076
077    public static boolean enumEntryNeedSubclass(BindingContext bindingContext, ClassDescriptor classDescriptor) {
078        return Boolean.TRUE.equals(bindingContext.get(ENUM_ENTRY_CLASS_NEED_SUBCLASS, classDescriptor));
079    }
080
081    @NotNull
082    public static JvmClassName classNameForScriptDescriptor(BindingContext bindingContext, @NotNull ScriptDescriptor scriptDescriptor) {
083        ClassDescriptor classDescriptor = bindingContext.get(CLASS_FOR_SCRIPT, scriptDescriptor);
084        //noinspection ConstantConditions
085        return fqn(bindingContext, classDescriptor);
086    }
087
088    @NotNull
089    public static JvmClassName classNameForScriptPsi(BindingContext bindingContext, @NotNull JetScript script) {
090        ScriptDescriptor scriptDescriptor = bindingContext.get(SCRIPT, script);
091        if (scriptDescriptor == null) {
092            throw new IllegalStateException("Script descriptor not found by PSI " + script);
093        }
094        return classNameForScriptDescriptor(bindingContext, scriptDescriptor);
095    }
096
097    public static ClassDescriptor enclosingClassDescriptor(BindingContext bindingContext, ClassDescriptor descriptor) {
098        CalculatedClosure closure = bindingContext.get(CLOSURE, descriptor);
099        return closure == null ? null : closure.getEnclosingClass();
100    }
101
102    @NotNull
103    public static ClassDescriptor anonymousClassForFunction(
104            @NotNull BindingContext bindingContext,
105            @NotNull FunctionDescriptor descriptor
106    ) {
107        //noinspection ConstantConditions
108        return bindingContext.get(CLASS_FOR_FUNCTION, descriptor);
109    }
110
111    @NotNull
112    private static JvmClassName fqn(@NotNull BindingContext bindingContext, @NotNull ClassDescriptor descriptor) {
113        //noinspection ConstantConditions
114        return bindingContext.get(FQN, descriptor);
115    }
116
117    @NotNull
118    public static JvmClassName classNameForAnonymousClass(@NotNull BindingContext bindingContext, @NotNull JetElement expression) {
119        if (expression instanceof JetObjectLiteralExpression) {
120            JetObjectLiteralExpression jetObjectLiteralExpression = (JetObjectLiteralExpression) expression;
121            expression = jetObjectLiteralExpression.getObjectDeclaration();
122        }
123
124        ClassDescriptor descriptor = bindingContext.get(CLASS, expression);
125        if (descriptor == null) {
126            SimpleFunctionDescriptor functionDescriptor = bindingContext.get(FUNCTION, expression);
127            assert functionDescriptor != null;
128            return classNameForAnonymousClass(bindingContext, functionDescriptor);
129        }
130
131        return fqn(bindingContext, descriptor);
132    }
133
134    @NotNull
135    public static JvmClassName classNameForAnonymousClass(@NotNull BindingContext bindingContext, @NotNull FunctionDescriptor descriptor) {
136        ClassDescriptor classDescriptor = anonymousClassForFunction(bindingContext, descriptor);
137        return fqn(bindingContext, classDescriptor);
138    }
139
140    public static void registerClassNameForScript(
141            BindingTrace bindingTrace,
142            @NotNull ScriptDescriptor scriptDescriptor,
143            @NotNull JvmClassName className
144    ) {
145        bindingTrace.record(SCRIPT_NAMES, className);
146
147        ClassDescriptorImpl classDescriptor = new ClassDescriptorImpl(
148                scriptDescriptor,
149                Collections.<AnnotationDescriptor>emptyList(),
150                Modality.FINAL,
151                Name.special("<script-" + className + ">"));
152        classDescriptor.initialize(
153                false,
154                Collections.<TypeParameterDescriptor>emptyList(),
155                Collections.singletonList(KotlinBuiltIns.getInstance().getAnyType()),
156                JetScope.EMPTY,
157                Collections.<ConstructorDescriptor>emptySet(),
158                null,
159                false);
160
161        recordClosure(bindingTrace, null, classDescriptor, null, className, false);
162
163        assert PsiCodegenPredictor.checkPredictedClassNameForFun(bindingTrace.getBindingContext(), scriptDescriptor, classDescriptor);
164        bindingTrace.record(CLASS_FOR_SCRIPT, scriptDescriptor, classDescriptor);
165    }
166
167    public static boolean canHaveOuter(BindingContext bindingContext, @NotNull ClassDescriptor classDescriptor) {
168        if (isSingleton(bindingContext, classDescriptor)) {
169            return false;
170        }
171
172        ClassDescriptor enclosing = enclosingClassDescriptor(bindingContext, classDescriptor);
173        if (enclosing == null) {
174            return false;
175        }
176
177        ClassKind kind = classDescriptor.getKind();
178        if (kind == ClassKind.CLASS) {
179            return classDescriptor.isInner() || !(classDescriptor.getContainingDeclaration() instanceof ClassDescriptor);
180        }
181        else if (kind == ClassKind.OBJECT) {
182            return !isSingleton(bindingContext, enclosing);
183        }
184        else {
185            return false;
186        }
187    }
188
189    public static boolean isSingleton(BindingContext bindingContext, @NotNull ClassDescriptor classDescriptor) {
190        ClassKind kind = classDescriptor.getKind();
191        if (kind == ClassKind.CLASS_OBJECT) {
192            return true;
193        }
194
195        if (kind == ClassKind.OBJECT && isObjectDeclaration(bindingContext, classDescriptor)) {
196            return true;
197        }
198
199        if (kind == ClassKind.ENUM_ENTRY) {
200            return true;
201        }
202
203        return false;
204    }
205
206    static void recordClosure(
207            BindingTrace bindingTrace,
208            @Nullable JetElement element,
209            ClassDescriptor classDescriptor,
210            @Nullable ClassDescriptor enclosing,
211            JvmClassName name,
212            boolean functionLiteral
213    ) {
214        JetDelegatorToSuperCall superCall = findSuperCall(bindingTrace.getBindingContext(), element);
215
216        CallableDescriptor enclosingReceiver = null;
217        if (classDescriptor.getContainingDeclaration() instanceof CallableDescriptor) {
218            enclosingReceiver = (CallableDescriptor) classDescriptor.getContainingDeclaration();
219            enclosingReceiver = enclosingReceiver instanceof PropertyAccessorDescriptor
220                                ? ((PropertyAccessorDescriptor) enclosingReceiver).getCorrespondingProperty()
221                                : enclosingReceiver;
222
223            if (enclosingReceiver.getReceiverParameter() == null) {
224                enclosingReceiver = null;
225            }
226        }
227
228        MutableClosure closure = new MutableClosure(superCall, enclosing, enclosingReceiver);
229
230        assert PsiCodegenPredictor.checkPredictedNameFromPsi(bindingTrace, classDescriptor, name);
231        bindingTrace.record(FQN, classDescriptor, name);
232        bindingTrace.record(CLOSURE, classDescriptor, closure);
233
234        // TODO: this is temporary before we have proper inner classes
235        if (canHaveOuter(bindingTrace.getBindingContext(), classDescriptor) && !functionLiteral) {
236            closure.setCaptureThis();
237        }
238
239        if (enclosing != null) {
240            recordInnerClass(bindingTrace, enclosing, classDescriptor);
241        }
242    }
243
244    private static void recordInnerClass(
245            @NotNull BindingTrace bindingTrace,
246            @NotNull ClassDescriptor outer,
247            @NotNull ClassDescriptor inner
248    ) {
249        Collection<ClassDescriptor> innerClasses = bindingTrace.get(INNER_CLASSES, outer);
250        if (innerClasses == null) {
251            innerClasses = new ArrayList<ClassDescriptor>();
252            bindingTrace.record(INNER_CLASSES, outer, innerClasses);
253        }
254        innerClasses.add(inner);
255    }
256
257    public static void registerClassNameForScript(BindingTrace bindingTrace, @NotNull JetScript jetScript, @NotNull JvmClassName className) {
258        ScriptDescriptor descriptor = bindingTrace.getBindingContext().get(SCRIPT, jetScript);
259        if (descriptor == null) {
260            throw new IllegalStateException("Descriptor is not found for PSI " + jetScript);
261        }
262        registerClassNameForScript(bindingTrace, descriptor, className);
263    }
264
265    @NotNull
266    public static Collection<JetFile> allFilesInNamespaces(BindingContext bindingContext, Collection<JetFile> files) {
267        // todo: we use Set and add given files but ignoring other scripts because something non-clear kept in binding
268        // for scripts especially in case of REPL
269
270        HashSet<FqName> names = new HashSet<FqName>();
271        for (JetFile file : files) {
272            if (!file.isScript()) {
273                names.add(JetPsiUtil.getFQName(file));
274            }
275        }
276
277        HashSet<JetFile> answer = new HashSet<JetFile>();
278        answer.addAll(files);
279
280        for (FqName name : names) {
281            NamespaceDescriptor namespaceDescriptor = bindingContext.get(BindingContext.FQNAME_TO_NAMESPACE_DESCRIPTOR, name);
282            Collection<JetFile> jetFiles = bindingContext.get(NAMESPACE_TO_FILES, namespaceDescriptor);
283            if (jetFiles != null)
284                answer.addAll(jetFiles);
285        }
286
287        List<JetFile> sortedAnswer = new ArrayList<JetFile>(answer);
288        Collections.sort(sortedAnswer, new Comparator<JetFile>() {
289            @NotNull
290            private String path(JetFile file) {
291                VirtualFile virtualFile = file.getVirtualFile();
292                assert virtualFile != null : "VirtualFile is null for JetFile: " + file.getName();
293                return virtualFile.getPath();
294            }
295
296            @Override
297            public int compare(JetFile first, JetFile second) {
298                return path(first).compareTo(path(second));
299            }
300        });
301
302        return sortedAnswer;
303    }
304
305    public static boolean isObjectLiteral(BindingContext bindingContext, ClassDescriptor declaration) {
306        PsiElement psiElement = descriptorToDeclaration(bindingContext, declaration);
307        if (psiElement instanceof JetObjectDeclaration && ((JetObjectDeclaration) psiElement).isObjectLiteral()) {
308            return true;
309        }
310        return false;
311    }
312
313    public static boolean isObjectDeclaration(BindingContext bindingContext, ClassDescriptor declaration) {
314        PsiElement psiElement = descriptorToDeclaration(bindingContext, declaration);
315        if (psiElement instanceof JetObjectDeclaration && !((JetObjectDeclaration) psiElement).isObjectLiteral()) {
316            return true;
317        }
318        return false;
319    }
320
321    public static boolean isLocalNamedFun(DeclarationDescriptor fd) {
322        if (fd instanceof FunctionDescriptor) {
323            FunctionDescriptor descriptor = (FunctionDescriptor) fd;
324            return descriptor.getVisibility() == Visibilities.LOCAL && !descriptor.getName().isSpecial();
325        }
326        return false;
327    }
328
329    @NotNull
330    public static JvmClassName getJvmInternalName(BindingTrace bindingTrace, @NotNull DeclarationDescriptor descriptor) {
331        descriptor = descriptor.getOriginal();
332        JvmClassName name = bindingTrace.getBindingContext().get(FQN, descriptor);
333        if (name != null) {
334            return name;
335        }
336
337        name = JvmClassName.byInternalName(getJvmInternalFQNameImpl(bindingTrace, descriptor));
338
339        assert PsiCodegenPredictor.checkPredictedNameFromPsi(bindingTrace, descriptor, name);
340        bindingTrace.record(FQN, descriptor, name);
341        return name;
342    }
343
344    private static String getJvmInternalFQNameImpl(BindingTrace bindingTrace, DeclarationDescriptor descriptor) {
345        if (descriptor instanceof FunctionDescriptor) {
346            throw new IllegalStateException("requested fq name for function: " + descriptor);
347        }
348
349        if (descriptor.getContainingDeclaration() instanceof ModuleDescriptor || descriptor instanceof ScriptDescriptor) {
350            return "";
351        }
352
353        if (descriptor instanceof ModuleDescriptor) {
354            throw new IllegalStateException("missed something");
355        }
356
357        if (descriptor instanceof ClassDescriptor) {
358            ClassDescriptor klass = (ClassDescriptor) descriptor;
359            if (klass.getKind() == ClassKind.OBJECT || klass.getKind() == ClassKind.CLASS_OBJECT) {
360                if (klass.getContainingDeclaration() instanceof ClassDescriptor) {
361                    ClassDescriptor containingKlass = (ClassDescriptor) klass.getContainingDeclaration();
362                    if (containingKlass.getKind() == ClassKind.ENUM_CLASS) {
363                        return getJvmInternalName(bindingTrace, containingKlass).getInternalName();
364                    }
365                    else {
366                        return getJvmInternalName(bindingTrace, containingKlass).getInternalName() + JvmAbi.CLASS_OBJECT_SUFFIX;
367                    }
368                }
369            }
370
371            JvmClassName name = bindingTrace.getBindingContext().get(FQN, descriptor);
372            if (name != null) {
373                return name.getInternalName();
374            }
375        }
376
377        DeclarationDescriptor container = descriptor.getContainingDeclaration();
378
379        if (container == null) {
380            throw new IllegalStateException("descriptor has no container: " + descriptor);
381        }
382
383        Name name = descriptor.getName();
384
385        String baseName = getJvmInternalName(bindingTrace, container).getInternalName();
386        if (!baseName.isEmpty()) {
387            return baseName + (container instanceof NamespaceDescriptor ? "/" : "$") + name.getIdentifier();
388        }
389
390        return name.getIdentifier();
391    }
392
393    public static boolean isVarCapturedInClosure(BindingContext bindingContext, DeclarationDescriptor descriptor) {
394        if (!(descriptor instanceof VariableDescriptor) || descriptor instanceof PropertyDescriptor) return false;
395        VariableDescriptor variableDescriptor = (VariableDescriptor) descriptor;
396        return bindingContext.get(CAPTURED_IN_CLOSURE, variableDescriptor) != null && variableDescriptor.isVar();
397    }
398
399    public static boolean hasThis0(BindingContext bindingContext, ClassDescriptor classDescriptor) {
400        //noinspection SuspiciousMethodCalls
401        CalculatedClosure closure = bindingContext.get(CLOSURE, classDescriptor);
402        return closure != null && closure.getCaptureThis() != null;
403    }
404
405    private static JetDelegatorToSuperCall findSuperCall(
406            BindingContext bindingContext,
407            JetElement classOrObject
408    ) {
409        if (!(classOrObject instanceof JetClassOrObject)) {
410            return null;
411        }
412
413        if (classOrObject instanceof JetClass && ((JetClass) classOrObject).isTrait()) {
414            return null;
415        }
416        for (JetDelegationSpecifier specifier : ((JetClassOrObject) classOrObject).getDelegationSpecifiers()) {
417            if (specifier instanceof JetDelegatorToSuperCall) {
418                JetType superType = bindingContext.get(TYPE, specifier.getTypeReference());
419                assert superType != null;
420                ClassDescriptor superClassDescriptor = (ClassDescriptor) superType.getConstructor().getDeclarationDescriptor();
421                assert superClassDescriptor != null;
422                if (!isInterface(superClassDescriptor)) {
423                    return (JetDelegatorToSuperCall) specifier;
424                }
425            }
426        }
427
428        return null;
429    }
430}