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