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