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    
017    package org.jetbrains.k2js.translate.context;
018    
019    import com.google.common.collect.Maps;
020    import com.google.dart.compiler.backend.js.ast.*;
021    import com.intellij.psi.PsiElement;
022    import com.intellij.psi.PsiFile;
023    import org.jetbrains.annotations.NotNull;
024    import org.jetbrains.annotations.Nullable;
025    import org.jetbrains.jet.lang.descriptors.*;
026    import org.jetbrains.jet.lang.resolve.BindingContext;
027    import org.jetbrains.jet.lang.resolve.BindingContextUtils;
028    import org.jetbrains.jet.lang.resolve.DescriptorUtils;
029    import org.jetbrains.k2js.config.EcmaVersion;
030    import org.jetbrains.k2js.config.LibrarySourcesConfig;
031    import org.jetbrains.k2js.translate.context.generator.Generator;
032    import org.jetbrains.k2js.translate.context.generator.Rule;
033    import org.jetbrains.k2js.translate.declaration.ClassDeclarationTranslator;
034    import org.jetbrains.k2js.translate.expression.LiteralFunctionTranslator;
035    import org.jetbrains.k2js.translate.intrinsic.Intrinsics;
036    import org.jetbrains.k2js.translate.utils.AnnotationsUtils;
037    import org.jetbrains.k2js.translate.utils.JsAstUtils;
038    import org.jetbrains.k2js.translate.utils.JsDescriptorUtils;
039    import org.jetbrains.k2js.translate.utils.PredefinedAnnotation;
040    
041    import java.util.Arrays;
042    import java.util.Collection;
043    import java.util.Comparator;
044    import java.util.Map;
045    
046    import static org.jetbrains.k2js.translate.utils.AnnotationsUtils.*;
047    import static org.jetbrains.k2js.translate.utils.BindingUtils.isObjectDeclaration;
048    import static org.jetbrains.k2js.translate.utils.JsDescriptorUtils.*;
049    
050    /**
051     * Aggregates all the static parts of the context.
052     */
053    public final class StaticContext {
054    
055        public static StaticContext generateStaticContext(@NotNull BindingContext bindingContext, @NotNull EcmaVersion ecmaVersion) {
056            JsProgram program = new JsProgram("main");
057            Namer namer = Namer.newInstance(program.getRootScope());
058            Intrinsics intrinsics = new Intrinsics();
059            StandardClasses standardClasses = StandardClasses.bindImplementations(namer.getKotlinScope());
060            return new StaticContext(program, bindingContext, namer, intrinsics, standardClasses, program.getRootScope(), ecmaVersion);
061        }
062    
063        @NotNull
064        private final JsProgram program;
065    
066        @NotNull
067        private final BindingContext bindingContext;
068        @NotNull
069        private final Namer namer;
070    
071        @NotNull
072        private final Intrinsics intrinsics;
073    
074        @NotNull
075        private final StandardClasses standardClasses;
076    
077        @NotNull
078        private final JsScope rootScope;
079    
080        @NotNull
081        private final Generator<JsName> names = new NameGenerator();
082        @NotNull
083        private final Generator<JsScope> scopes = new ScopeGenerator();
084        @NotNull
085        private final Generator<JsNameRef> qualifiers = new QualifierGenerator();
086        @NotNull
087        private final Generator<Boolean> qualifierIsNull = new QualifierIsNullGenerator();
088    
089        @NotNull
090        private final Map<JsScope, JsFunction> scopeToFunction = Maps.newHashMap();
091    
092        @NotNull
093        private final EcmaVersion ecmaVersion;
094    
095        @NotNull
096        private LiteralFunctionTranslator literalFunctionTranslator;
097        @NotNull
098        private ClassDeclarationTranslator classDeclarationTranslator;
099    
100        //TODO: too many parameters in constructor
101        private StaticContext(@NotNull JsProgram program, @NotNull BindingContext bindingContext,
102                @NotNull Namer namer, @NotNull Intrinsics intrinsics,
103                @NotNull StandardClasses standardClasses, @NotNull JsScope rootScope, @NotNull EcmaVersion ecmaVersion) {
104            this.program = program;
105            this.bindingContext = bindingContext;
106            this.namer = namer;
107            this.intrinsics = intrinsics;
108            this.rootScope = rootScope;
109            this.standardClasses = standardClasses;
110            this.ecmaVersion = ecmaVersion;
111        }
112    
113        public void initTranslators(TranslationContext programContext) {
114            literalFunctionTranslator = new LiteralFunctionTranslator(programContext);
115            classDeclarationTranslator = new ClassDeclarationTranslator(programContext);
116        }
117    
118        @NotNull
119        public LiteralFunctionTranslator getLiteralFunctionTranslator() {
120            return literalFunctionTranslator;
121        }
122    
123        @NotNull
124        public ClassDeclarationTranslator getClassDeclarationTranslator() {
125            return classDeclarationTranslator;
126        }
127    
128        public boolean isEcma5() {
129            return ecmaVersion == EcmaVersion.v5;
130        }
131    
132        @NotNull
133        public JsProgram getProgram() {
134            return program;
135        }
136    
137        @NotNull
138        public BindingContext getBindingContext() {
139            return bindingContext;
140        }
141    
142        @NotNull
143        public Intrinsics getIntrinsics() {
144            return intrinsics;
145        }
146    
147        @NotNull
148        public Namer getNamer() {
149            return namer;
150        }
151    
152        @NotNull
153        public JsScope getRootScope() {
154            return rootScope;
155        }
156    
157        @NotNull
158        public JsScope getScopeForDescriptor(@NotNull DeclarationDescriptor descriptor) {
159            JsScope scope = scopes.get(descriptor.getOriginal());
160            assert scope != null : "Must have a scope for descriptor";
161            return scope;
162        }
163    
164        @NotNull
165        public JsFunction getFunctionWithScope(@NotNull CallableDescriptor descriptor) {
166            JsScope scope = getScopeForDescriptor(descriptor);
167            JsFunction function = scopeToFunction.get(scope);
168            assert scope.equals(function.getScope()) : "Inconsistency.";
169            return function;
170        }
171    
172        @NotNull
173        public JsNameRef getQualifiedReference(@NotNull DeclarationDescriptor descriptor) {
174            ClassDescriptor classDescriptor;
175            if (descriptor instanceof ConstructorDescriptor) {
176                classDescriptor = ((ConstructorDescriptor) descriptor).getContainingDeclaration();
177            }
178            else if (descriptor instanceof ClassDescriptor) {
179                classDescriptor = (ClassDescriptor) descriptor;
180            }
181            else {
182                classDescriptor = null;
183            }
184    
185            if (classDescriptor != null) {
186                JsNameRef reference = classDeclarationTranslator.getQualifiedReference((classDescriptor));
187                if (reference != null) {
188                    return reference;
189                }
190            }
191            return new JsNameRef(getNameForDescriptor(descriptor), getQualifierForDescriptor(descriptor));
192        }
193    
194        @NotNull
195        public JsName getNameForDescriptor(@NotNull DeclarationDescriptor descriptor) {
196            JsName name = names.get(descriptor.getOriginal());
197            assert name != null : "Must have name for descriptor";
198            return name;
199        }
200    
201        private final class NameGenerator extends Generator<JsName> {
202            private JsName declareName(DeclarationDescriptor descriptor, String name) {
203                JsScope scope = getEnclosingScope(descriptor);
204                // ecma 5 property name never declares as obfuscatable:
205                // 1) property cannot be overloaded, so, name collision is not possible
206                // 2) main reason: if property doesn't have any custom accessor, value holder will have the same name as accessor, so, the same name will be declared more than once
207                return isEcma5() ? scope.declareName(name) : scope.declareFreshName(name);
208            }
209    
210            public NameGenerator() {
211                Rule<JsName> namesForStandardClasses = new Rule<JsName>() {
212                    @Override
213                    @Nullable
214                    public JsName apply(@NotNull DeclarationDescriptor data) {
215                        if (!standardClasses.isStandardObject(data)) {
216                            return null;
217                        }
218                        return standardClasses.getStandardObjectName(data);
219                    }
220                };
221                Rule<JsName> namespacesShouldBeDefinedInRootScope = new Rule<JsName>() {
222                    @Override
223                    @Nullable
224                    public JsName apply(@NotNull DeclarationDescriptor descriptor) {
225                        if (!(descriptor instanceof NamespaceDescriptor)) {
226                            return null;
227                        }
228    
229                        String name = Namer.generateNamespaceName(descriptor);
230                        return getRootScope().declareName(name);
231                    }
232                };
233                Rule<JsName> memberDeclarationsInsideParentsScope = new Rule<JsName>() {
234                    @Override
235                    @Nullable
236                    public JsName apply(@NotNull DeclarationDescriptor descriptor) {
237                        JsScope scope = getEnclosingScope(descriptor);
238                        DeclarationDescriptor declaration = descriptor.getContainingDeclaration();
239                        if (!(descriptor instanceof FunctionDescriptor) || !(declaration instanceof ClassDescriptor)) {
240                            return scope.declareFreshName(descriptor.getName().asString());
241                        }
242    
243                        Collection<FunctionDescriptor> functions =
244                                ((ClassDescriptor) declaration).getDefaultType().getMemberScope().getFunctions(descriptor.getName());
245                        String name = descriptor.getName().asString();
246                        int counter = -1;
247                        if (functions.size() > 1) {
248                            // see testOverloadedFun
249                            FunctionDescriptor[] sorted = functions.toArray(new FunctionDescriptor[functions.size()]);
250                            Arrays.sort(sorted, new Comparator<FunctionDescriptor>() {
251                                @Override
252                                public int compare(@NotNull FunctionDescriptor a, @NotNull FunctionDescriptor b) {
253                                    Integer result = Visibilities.compare(b.getVisibility(), a.getVisibility());
254                                    if (result == null) {
255                                        return 0;
256                                    }
257                                    else if (result == 0) {
258                                        // open fun > not open fun
259                                        int aWeight = a.getModality().isOverridable() ? 1 : 0;
260                                        int bWeight = b.getModality().isOverridable() ? 1 : 0;
261                                        return bWeight - aWeight;
262                                    }
263    
264                                    return result;
265                                }
266                            });
267                            for (FunctionDescriptor function : sorted) {
268                                if (function == descriptor) {
269                                    break;
270                                }
271                                counter++;
272                            }
273                        }
274    
275                        return scope.declareName(counter == -1 ? name : name + '_' + counter);
276                    }
277                };
278                Rule<JsName> constructorHasTheSameNameAsTheClass = new Rule<JsName>() {
279                    @Override
280                    public JsName apply(@NotNull DeclarationDescriptor descriptor) {
281                        if (!(descriptor instanceof ConstructorDescriptor)) {
282                            return null;
283                        }
284                        ClassDescriptor containingClass = getContainingClass(descriptor);
285                        assert containingClass != null : "Can't have constructor without a class";
286                        return getNameForDescriptor(containingClass);
287                    }
288                };
289                Rule<JsName> accessorsHasNamesWithSpecialPrefixes = new Rule<JsName>() {
290                    @Override
291                    public JsName apply(@NotNull DeclarationDescriptor descriptor) {
292                        if (!(descriptor instanceof PropertyAccessorDescriptor)) {
293                            return null;
294                        }
295    
296                        PropertyAccessorDescriptor accessorDescriptor = (PropertyAccessorDescriptor) descriptor;
297                        String propertyName = accessorDescriptor.getCorrespondingProperty().getName().asString();
298                        if (isObjectDeclaration(accessorDescriptor.getCorrespondingProperty())) {
299                            return declareName(descriptor, propertyName);
300                        }
301    
302                        boolean isGetter = descriptor instanceof PropertyGetterDescriptor;
303                        String accessorName = Namer.getNameForAccessor(propertyName, isGetter,
304                                                                       accessorDescriptor.getReceiverParameter() == null && isEcma5());
305                        return declareName(descriptor, accessorName);
306                    }
307                };
308    
309                Rule<JsName> predefinedObjectsHasUnobfuscatableNames = new Rule<JsName>() {
310                    @Override
311                    public JsName apply(@NotNull DeclarationDescriptor descriptor) {
312                        for (PredefinedAnnotation annotation : PredefinedAnnotation.values()) {
313                            if (!hasAnnotationOrInsideAnnotatedClass(descriptor, annotation)) {
314                                continue;
315                            }
316                            String name = getNameForAnnotatedObject(descriptor, annotation);
317                            name = (name != null) ? name : descriptor.getName().asString();
318                            return getEnclosingScope(descriptor).declareName(name);
319                        }
320                        return null;
321                    }
322                };
323                Rule<JsName> propertiesCorrespondToSpeciallyTreatedBackingFieldNames = new Rule<JsName>() {
324                    @Override
325                    public JsName apply(@NotNull DeclarationDescriptor descriptor) {
326                        if (!(descriptor instanceof PropertyDescriptor)) {
327                            return null;
328                        }
329    
330                        String name = descriptor.getName().asString();
331                        if (!isEcma5() || JsDescriptorUtils.isAsPrivate((PropertyDescriptor) descriptor)) {
332                            name = Namer.getKotlinBackingFieldName(name);
333                        }
334    
335                        return declareName(descriptor, name);
336                    }
337                };
338                Rule<JsName> overridingDescriptorsReferToOriginalName = new Rule<JsName>() {
339                    @Override
340                    public JsName apply(@NotNull DeclarationDescriptor descriptor) {
341                        //TODO: refactor
342                        if (!(descriptor instanceof FunctionDescriptor)) {
343                            return null;
344                        }
345                        FunctionDescriptor overriddenDescriptor = getOverriddenDescriptor((FunctionDescriptor) descriptor);
346                        if (overriddenDescriptor == null) {
347                            return null;
348                        }
349    
350                        JsScope scope = getEnclosingScope(descriptor);
351                        JsName result = getNameForDescriptor(overriddenDescriptor);
352                        scope.declareName(result.getIdent());
353                        return result;
354                    }
355                };
356                addRule(namesForStandardClasses);
357                addRule(constructorHasTheSameNameAsTheClass);
358                addRule(predefinedObjectsHasUnobfuscatableNames);
359                addRule(propertiesCorrespondToSpeciallyTreatedBackingFieldNames);
360                addRule(namespacesShouldBeDefinedInRootScope);
361                addRule(overridingDescriptorsReferToOriginalName);
362                addRule(accessorsHasNamesWithSpecialPrefixes);
363                addRule(memberDeclarationsInsideParentsScope);
364            }
365        }
366    
367        @NotNull
368        private JsScope getEnclosingScope(@NotNull DeclarationDescriptor descriptor) {
369            DeclarationDescriptor containingDeclaration = getContainingDeclaration(descriptor);
370            return getScopeForDescriptor(containingDeclaration.getOriginal());
371        }
372    
373        private final class ScopeGenerator extends Generator<JsScope> {
374    
375            public ScopeGenerator() {
376                Rule<JsScope> generateNewScopesForClassesWithNoAncestors = new Rule<JsScope>() {
377                    @Override
378                    public JsScope apply(@NotNull DeclarationDescriptor descriptor) {
379                        if (!(descriptor instanceof ClassDescriptor)) {
380                            return null;
381                        }
382                        if (getSuperclass((ClassDescriptor) descriptor) == null) {
383                            return getRootScope().innerScope("Scope for class " + descriptor.getName());
384                        }
385                        return null;
386                    }
387                };
388                Rule<JsScope> generateInnerScopesForDerivedClasses = new Rule<JsScope>() {
389                    @Override
390                    public JsScope apply(@NotNull DeclarationDescriptor descriptor) {
391                        if (!(descriptor instanceof ClassDescriptor)) {
392                            return null;
393                        }
394                        ClassDescriptor superclass = getSuperclass((ClassDescriptor) descriptor);
395                        if (superclass == null) {
396                            return null;
397                        }
398                        return getScopeForDescriptor(superclass).innerScope("Scope for class " + descriptor.getName());
399                    }
400                };
401                Rule<JsScope> generateNewScopesForNamespaceDescriptors = new Rule<JsScope>() {
402                    @Override
403                    public JsScope apply(@NotNull DeclarationDescriptor descriptor) {
404                        if (!(descriptor instanceof NamespaceDescriptor)) {
405                            return null;
406                        }
407                        return getRootScope().innerScope("Namespace " + descriptor.getName());
408                    }
409                };
410                //TODO: never get there
411                Rule<JsScope> generateInnerScopesForMembers = new Rule<JsScope>() {
412                    @Override
413                    public JsScope apply(@NotNull DeclarationDescriptor descriptor) {
414                        JsScope enclosingScope = getEnclosingScope(descriptor);
415                        return enclosingScope.innerScope("Scope for member " + descriptor.getName());
416                    }
417                };
418                Rule<JsScope> createFunctionObjectsForCallableDescriptors = new Rule<JsScope>() {
419                    @Override
420                    public JsScope apply(@NotNull DeclarationDescriptor descriptor) {
421                        if (!(descriptor instanceof CallableDescriptor)) {
422                            return null;
423                        }
424                        JsScope enclosingScope = getEnclosingScope(descriptor);
425    
426                        JsFunction correspondingFunction = JsAstUtils.createFunctionWithEmptyBody(enclosingScope);
427                        assert (!scopeToFunction.containsKey(correspondingFunction.getScope())) : "Scope to function value overridden for " + descriptor;
428                        scopeToFunction.put(correspondingFunction.getScope(), correspondingFunction);
429                        return correspondingFunction.getScope();
430                    }
431                };
432                addRule(createFunctionObjectsForCallableDescriptors);
433                addRule(generateNewScopesForClassesWithNoAncestors);
434                addRule(generateInnerScopesForDerivedClasses);
435                addRule(generateNewScopesForNamespaceDescriptors);
436                addRule(generateInnerScopesForMembers);
437            }
438        }
439    
440        @Nullable
441        public JsNameRef getQualifierForDescriptor(@NotNull DeclarationDescriptor descriptor) {
442            if (qualifierIsNull.get(descriptor.getOriginal()) != null) {
443                return null;
444            }
445            return qualifiers.get(descriptor.getOriginal());
446        }
447    
448        private final class QualifierGenerator extends Generator<JsNameRef> {
449            public QualifierGenerator() {
450                Rule<JsNameRef> standardObjectsHaveKotlinQualifier = new Rule<JsNameRef>() {
451                    @Override
452                    public JsNameRef apply(@NotNull DeclarationDescriptor descriptor) {
453                        if (!standardClasses.isStandardObject(descriptor)) {
454                            return null;
455                        }
456                        return namer.kotlinObject();
457                    }
458                };
459                //TODO: review and refactor
460                Rule<JsNameRef> namespaceLevelDeclarationsHaveEnclosingNamespacesNamesAsQualifier = new Rule<JsNameRef>() {
461                    @Override
462                    public JsNameRef apply(@NotNull DeclarationDescriptor descriptor) {
463                        DeclarationDescriptor containingDescriptor = getContainingDeclaration(descriptor);
464                        if (!(containingDescriptor instanceof NamespaceDescriptor)) {
465                            return null;
466                        }
467    
468                        JsNameRef result = new JsNameRef(getNameForDescriptor(containingDescriptor));
469                        if (DescriptorUtils.isRootNamespace((NamespaceDescriptor) containingDescriptor)) {
470                            return result;
471                        }
472    
473                        JsNameRef qualifier = result;
474                        while ((containingDescriptor = getContainingDeclaration(containingDescriptor)) instanceof NamespaceDescriptor &&
475                               !DescriptorUtils.isRootNamespace((NamespaceDescriptor) containingDescriptor)) {
476                            JsNameRef ref = getNameForDescriptor(containingDescriptor).makeRef();
477                            qualifier.setQualifier(ref);
478                            qualifier = ref;
479                        }
480    
481                        PsiElement element = BindingContextUtils.descriptorToDeclaration(bindingContext, descriptor);
482                        if (element == null && descriptor instanceof PropertyAccessorDescriptor) {
483                            element = BindingContextUtils.descriptorToDeclaration(bindingContext, ((PropertyAccessorDescriptor) descriptor)
484                                    .getCorrespondingProperty());
485                        }
486    
487                        if (element != null) {
488                            PsiFile file = element.getContainingFile();
489                            String moduleName = file.getUserData(LibrarySourcesConfig.EXTERNAL_MODULE_NAME);
490                            if (LibrarySourcesConfig.UNKNOWN_EXTERNAL_MODULE_NAME.equals(moduleName)) {
491                                return null;
492                            }
493                            else if (moduleName != null) {
494                                qualifier.setQualifier(new JsArrayAccess(namer.kotlin("modules"), program.getStringLiteral(moduleName)));
495                            }
496                        }
497    
498                        if (qualifier.getQualifier() == null) {
499                            qualifier.setQualifier(new JsNameRef(Namer.getRootNamespaceName()));
500                        }
501    
502                        return result;
503                    }
504                };
505                Rule<JsNameRef> constructorHaveTheSameQualifierAsTheClass = new Rule<JsNameRef>() {
506                    @Override
507                    public JsNameRef apply(@NotNull DeclarationDescriptor descriptor) {
508                        if (!(descriptor instanceof ConstructorDescriptor)) {
509                            return null;
510                        }
511                        ClassDescriptor containingClass = getContainingClass(descriptor);
512                        assert containingClass != null : "Can't have constructor without a class";
513                        return getQualifierForDescriptor(containingClass);
514                    }
515                };
516                Rule<JsNameRef> libraryObjectsHaveKotlinQualifier = new Rule<JsNameRef>() {
517                    @Override
518                    public JsNameRef apply(@NotNull DeclarationDescriptor descriptor) {
519                        if (isLibraryObject(descriptor)) {
520                            return namer.kotlinObject();
521                        }
522                        return null;
523                    }
524                };
525                addRule(libraryObjectsHaveKotlinQualifier);
526                addRule(constructorHaveTheSameQualifierAsTheClass);
527                addRule(standardObjectsHaveKotlinQualifier);
528                addRule(namespaceLevelDeclarationsHaveEnclosingNamespacesNamesAsQualifier);
529            }
530        }
531    
532        private static class QualifierIsNullGenerator extends Generator<Boolean> {
533    
534            private QualifierIsNullGenerator() {
535                Rule<Boolean> propertiesHaveNoQualifiers = new Rule<Boolean>() {
536                    @Override
537                    public Boolean apply(@NotNull DeclarationDescriptor descriptor) {
538                        if (!(descriptor instanceof PropertyDescriptor)) {
539                            return null;
540                        }
541                        return true;
542                    }
543                };
544                //TODO: hack!
545                Rule<Boolean> nativeObjectsHaveNoQualifiers = new Rule<Boolean>() {
546                    @Override
547                    public Boolean apply(@NotNull DeclarationDescriptor descriptor) {
548                        if (!AnnotationsUtils.isNativeObject(descriptor)) {
549                            return null;
550                        }
551                        return true;
552                    }
553                };
554                Rule<Boolean> topLevelNamespaceHaveNoQualifier = new Rule<Boolean>() {
555                    @Override
556                    public Boolean apply(@NotNull DeclarationDescriptor descriptor) {
557                        if (descriptor instanceof NamespaceDescriptor && DescriptorUtils.isRootNamespace((NamespaceDescriptor) descriptor)) {
558                            return true;
559                        }
560                        return null;
561                    }
562                };
563                addRule(topLevelNamespaceHaveNoQualifier);
564                addRule(propertiesHaveNoQualifiers);
565                addRule(nativeObjectsHaveNoQualifiers);
566            }
567        }
568    }