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.openapi.util.Factory;
022    import com.intellij.psi.PsiElement;
023    import com.intellij.util.containers.ContainerUtil;
024    import org.jetbrains.annotations.NotNull;
025    import org.jetbrains.annotations.Nullable;
026    import org.jetbrains.jet.lang.descriptors.*;
027    import org.jetbrains.jet.lang.resolve.BindingContext;
028    import org.jetbrains.jet.lang.resolve.BindingContextUtils;
029    import org.jetbrains.jet.lang.resolve.name.FqName;
030    import org.jetbrains.k2js.config.EcmaVersion;
031    import org.jetbrains.k2js.config.LibrarySourcesConfig;
032    import org.jetbrains.k2js.translate.context.generator.Generator;
033    import org.jetbrains.k2js.translate.context.generator.Rule;
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.Map;
039    
040    import static org.jetbrains.k2js.translate.utils.AnnotationsUtils.getNameForAnnotatedObjectWithOverrides;
041    import static org.jetbrains.k2js.translate.utils.AnnotationsUtils.isLibraryObject;
042    import static org.jetbrains.k2js.translate.utils.JsDescriptorUtils.*;
043    import static org.jetbrains.k2js.translate.utils.TranslationUtils.getMangledName;
044    import static org.jetbrains.k2js.translate.utils.TranslationUtils.getSuggestedName;
045    
046    /**
047     * Aggregates all the static parts of the context.
048     */
049    public final class StaticContext {
050    
051        public static StaticContext generateStaticContext(@NotNull BindingContext bindingContext, @NotNull EcmaVersion ecmaVersion) {
052            JsProgram program = new JsProgram("main");
053            Namer namer = Namer.newInstance(program.getRootScope());
054            Intrinsics intrinsics = new Intrinsics();
055            StandardClasses standardClasses = StandardClasses.bindImplementations(namer.getKotlinScope());
056            return new StaticContext(program, bindingContext, namer, intrinsics, standardClasses, program.getRootScope(), ecmaVersion);
057        }
058    
059        @NotNull
060        private final JsProgram program;
061    
062        @NotNull
063        private final BindingContext bindingContext;
064        @NotNull
065        private final Namer namer;
066    
067        @NotNull
068        private final Intrinsics intrinsics;
069    
070        @NotNull
071        private final StandardClasses standardClasses;
072    
073        @NotNull
074        private final JsScope rootScope;
075    
076        @NotNull
077        private final Generator<JsName> names = new NameGenerator();
078        @NotNull
079        private final Map<FqName, JsName> packageNames = Maps.newHashMap();
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        //TODO: too many parameters in constructor
094        private StaticContext(@NotNull JsProgram program, @NotNull BindingContext bindingContext,
095                @NotNull Namer namer, @NotNull Intrinsics intrinsics,
096                @NotNull StandardClasses standardClasses, @NotNull JsScope rootScope, @NotNull EcmaVersion ecmaVersion) {
097            this.program = program;
098            this.bindingContext = bindingContext;
099            this.namer = namer;
100            this.intrinsics = intrinsics;
101            this.rootScope = rootScope;
102            this.standardClasses = standardClasses;
103            this.ecmaVersion = ecmaVersion;
104        }
105    
106        public boolean isEcma5() {
107            return ecmaVersion == EcmaVersion.v5;
108        }
109    
110        @NotNull
111        public JsProgram getProgram() {
112            return program;
113        }
114    
115        @NotNull
116        public BindingContext getBindingContext() {
117            return bindingContext;
118        }
119    
120        @NotNull
121        public Intrinsics getIntrinsics() {
122            return intrinsics;
123        }
124    
125        @NotNull
126        public Namer getNamer() {
127            return namer;
128        }
129    
130        @NotNull
131        public JsScope getRootScope() {
132            return rootScope;
133        }
134    
135        @NotNull
136        public JsScope getScopeForDescriptor(@NotNull DeclarationDescriptor descriptor) {
137            JsScope scope = scopes.get(descriptor.getOriginal());
138            assert scope != null : "Must have a scope for descriptor";
139            return scope;
140        }
141    
142        @NotNull
143        public JsFunction getFunctionWithScope(@NotNull CallableDescriptor descriptor) {
144            JsScope scope = getScopeForDescriptor(descriptor);
145            JsFunction function = scopeToFunction.get(scope);
146            assert scope.equals(function.getScope()) : "Inconsistency.";
147            return function;
148        }
149    
150        @NotNull
151        public JsNameRef getQualifiedReference(@NotNull DeclarationDescriptor descriptor) {
152            if (descriptor instanceof PackageViewDescriptor) {
153                return getQualifiedReference(((PackageViewDescriptor) descriptor).getFqName());
154            }
155            if (descriptor instanceof PackageFragmentDescriptor) {
156                return getQualifiedReference(((PackageFragmentDescriptor) descriptor).getFqName());
157            }
158    
159            return new JsNameRef(getNameForDescriptor(descriptor), getQualifierForDescriptor(descriptor));
160        }
161    
162        @NotNull
163        public JsNameRef getQualifiedReference(@NotNull FqName packageFqName) {
164            return new JsNameRef(getNameForPackage(packageFqName),
165                                 packageFqName.isRoot() ? null : getQualifierForParentPackage(packageFqName.parent()));
166        }
167    
168        @NotNull
169        public JsName getNameForDescriptor(@NotNull DeclarationDescriptor descriptor) {
170            JsName name = names.get(descriptor.getOriginal());
171            assert name != null : "Must have name for descriptor";
172            return name;
173        }
174    
175        @NotNull
176        public JsName getNameForPackage(@NotNull final FqName packageFqName) {
177            return ContainerUtil.getOrCreate(packageNames, packageFqName, new Factory<JsName>() {
178                @Override
179                public JsName create() {
180                    String name = Namer.generatePackageName(packageFqName);
181                    return getRootScope().declareName(name);
182                }
183            });
184        }
185    
186        @NotNull
187        private JsNameRef getQualifierForParentPackage(@NotNull FqName packageFqName) {
188            JsNameRef result = null;
189            JsNameRef qualifier = null;
190    
191            for (FqName pathElement : ContainerUtil.reverse(packageFqName.path())) {
192                JsNameRef ref = getNameForPackage(pathElement).makeRef();
193    
194                if (qualifier == null) {
195                    result = ref;
196                }
197                else {
198                    qualifier.setQualifier(ref);
199                }
200    
201                qualifier = ref;
202            }
203    
204            assert result != null : "didn't iterate: " + packageFqName;
205            return result;
206        }
207    
208        private final class NameGenerator extends Generator<JsName> {
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> memberDeclarationsInsideParentsScope = new Rule<JsName>() {
222                    @Override
223                    @Nullable
224                    public JsName apply(@NotNull DeclarationDescriptor descriptor) {
225                        JsScope scope = getEnclosingScope(descriptor);
226                        return scope.declareFreshName(getSuggestedName(descriptor));
227                    }
228                };
229                Rule<JsName> constructorHasTheSameNameAsTheClass = new Rule<JsName>() {
230                    @Override
231                    public JsName apply(@NotNull DeclarationDescriptor descriptor) {
232                        if (!(descriptor instanceof ConstructorDescriptor)) {
233                            return null;
234                        }
235                        ClassDescriptor containingClass = getContainingClass(descriptor);
236                        assert containingClass != null : "Can't have constructor without a class";
237                        return getNameForDescriptor(containingClass);
238                    }
239                };
240    
241                // ecma 5 property name never declares as obfuscatable:
242                // 1) property cannot be overloaded, so, name collision is not possible
243                // 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
244                //
245                // But extension property may obfuscatable, because transform into function. Example: String.foo = 1, Int.foo = 2
246                Rule<JsName> propertyOrPropertyAccessor = new Rule<JsName>() {
247                    @Override
248                    public JsName apply(@NotNull DeclarationDescriptor descriptor) {
249                        PropertyDescriptor propertyDescriptor;
250                        if (descriptor instanceof PropertyAccessorDescriptor) {
251                            propertyDescriptor = ((PropertyAccessorDescriptor) descriptor).getCorrespondingProperty();
252                        }
253                        else if (descriptor instanceof PropertyDescriptor) {
254                            propertyDescriptor = (PropertyDescriptor) descriptor;
255                        }
256                        else {
257                            return null;
258                        }
259    
260                        String nameFromAnnotation = getNameForAnnotatedObjectWithOverrides(propertyDescriptor);
261                        if (nameFromAnnotation != null) {
262                            return declarePropertyOrPropertyAccessorName(descriptor, nameFromAnnotation, false);
263                        }
264    
265                        String propertyName =  propertyDescriptor.getName().asString();
266    
267                        if (!isExtension(propertyDescriptor)) {
268                            if (propertyDescriptor.getVisibility() == Visibilities.PRIVATE) {
269                                propertyName = getMangledName(propertyDescriptor, propertyName);
270                            }
271                            return declarePropertyOrPropertyAccessorName(descriptor, propertyName, false);
272                        } else {
273                            if (descriptor instanceof PropertyDescriptor) {
274                                return declarePropertyOrPropertyAccessorName(descriptor, propertyName, true);
275                            } else {
276                                String propertyJsName = getNameForDescriptor(propertyDescriptor).getIdent();
277                                boolean isGetter = descriptor instanceof PropertyGetterDescriptor;
278                                String accessorName = Namer.getNameForAccessor(propertyJsName, isGetter, false);
279                                return declarePropertyOrPropertyAccessorName(descriptor, accessorName, false);
280                            }
281                        }
282                    }
283                };
284    
285                Rule<JsName> predefinedObjectsHasUnobfuscatableNames = new Rule<JsName>() {
286                    @Override
287                    public JsName apply(@NotNull DeclarationDescriptor descriptor) {
288                        // The mixing of override and rename by annotation(e.g. native) is forbidden.
289                        if (descriptor instanceof CallableMemberDescriptor &&
290                            !((CallableMemberDescriptor) descriptor).getOverriddenDescriptors().isEmpty()) {
291                            return null;
292                        }
293    
294                        String name = getNameForAnnotatedObjectWithOverrides(descriptor);
295                        if (name != null) return getEnclosingScope(descriptor).declareName(name);
296                        return null;
297                    }
298                };
299    
300                Rule<JsName> overridingDescriptorsReferToOriginalName = new Rule<JsName>() {
301                    @Override
302                    public JsName apply(@NotNull DeclarationDescriptor descriptor) {
303                        //TODO: refactor
304                        if (!(descriptor instanceof FunctionDescriptor)) {
305                            return null;
306                        }
307                        FunctionDescriptor overriddenDescriptor = getOverriddenDescriptor((FunctionDescriptor) descriptor);
308                        if (overriddenDescriptor == null) {
309                            return null;
310                        }
311    
312                        JsScope scope = getEnclosingScope(descriptor);
313                        JsName result = getNameForDescriptor(overriddenDescriptor);
314                        scope.declareName(result.getIdent());
315                        return result;
316                    }
317                };
318                addRule(namesForStandardClasses);
319                addRule(constructorHasTheSameNameAsTheClass);
320                addRule(propertyOrPropertyAccessor);
321                addRule(predefinedObjectsHasUnobfuscatableNames);
322                addRule(overridingDescriptorsReferToOriginalName);
323                addRule(memberDeclarationsInsideParentsScope);
324            }
325        }
326    
327        @NotNull
328        public JsName declarePropertyOrPropertyAccessorName(@NotNull DeclarationDescriptor descriptor, @NotNull String name, boolean fresh) {
329            JsScope scope = getEnclosingScope(descriptor);
330            return fresh ? scope.declareFreshName(name) : scope.declareName(name);
331        }
332    
333        @NotNull
334        private JsScope getEnclosingScope(@NotNull DeclarationDescriptor descriptor) {
335            DeclarationDescriptor containingDeclaration = getContainingDeclaration(descriptor);
336            return getScopeForDescriptor(containingDeclaration.getOriginal());
337        }
338    
339        private final class ScopeGenerator extends Generator<JsScope> {
340    
341            public ScopeGenerator() {
342                Rule<JsScope> generateNewScopesForClassesWithNoAncestors = new Rule<JsScope>() {
343                    @Override
344                    public JsScope apply(@NotNull DeclarationDescriptor descriptor) {
345                        if (!(descriptor instanceof ClassDescriptor)) {
346                            return null;
347                        }
348                        if (getSuperclass((ClassDescriptor) descriptor) == null) {
349                            return getRootScope().innerScope("Scope for class " + descriptor.getName());
350                        }
351                        return null;
352                    }
353                };
354                Rule<JsScope> generateInnerScopesForDerivedClasses = new Rule<JsScope>() {
355                    @Override
356                    public JsScope apply(@NotNull DeclarationDescriptor descriptor) {
357                        if (!(descriptor instanceof ClassDescriptor)) {
358                            return null;
359                        }
360                        ClassDescriptor superclass = getSuperclass((ClassDescriptor) descriptor);
361                        if (superclass == null) {
362                            return null;
363                        }
364                        return getScopeForDescriptor(superclass).innerScope("Scope for class " + descriptor.getName());
365                    }
366                };
367                Rule<JsScope> generateNewScopesForPackageDescriptors = new Rule<JsScope>() {
368                    @Override
369                    public JsScope apply(@NotNull DeclarationDescriptor descriptor) {
370                        if (!(descriptor instanceof PackageFragmentDescriptor)) {
371                            return null;
372                        }
373                        return getRootScope().innerScope("Package " + descriptor.getName());
374                    }
375                };
376                //TODO: never get there
377                Rule<JsScope> generateInnerScopesForMembers = new Rule<JsScope>() {
378                    @Override
379                    public JsScope apply(@NotNull DeclarationDescriptor descriptor) {
380                        JsScope enclosingScope = getEnclosingScope(descriptor);
381                        return enclosingScope.innerScope("Scope for member " + descriptor.getName());
382                    }
383                };
384                Rule<JsScope> createFunctionObjectsForCallableDescriptors = new Rule<JsScope>() {
385                    @Override
386                    public JsScope apply(@NotNull DeclarationDescriptor descriptor) {
387                        if (!(descriptor instanceof CallableDescriptor)) {
388                            return null;
389                        }
390                        JsScope enclosingScope = getEnclosingScope(descriptor);
391    
392                        JsFunction correspondingFunction = JsAstUtils.createFunctionWithEmptyBody(enclosingScope);
393                        assert (!scopeToFunction.containsKey(correspondingFunction.getScope())) : "Scope to function value overridden for " + descriptor;
394                        scopeToFunction.put(correspondingFunction.getScope(), correspondingFunction);
395                        return correspondingFunction.getScope();
396                    }
397                };
398                addRule(createFunctionObjectsForCallableDescriptors);
399                addRule(generateNewScopesForClassesWithNoAncestors);
400                addRule(generateInnerScopesForDerivedClasses);
401                addRule(generateNewScopesForPackageDescriptors);
402                addRule(generateInnerScopesForMembers);
403            }
404        }
405    
406        @Nullable
407        public JsNameRef getQualifierForDescriptor(@NotNull DeclarationDescriptor descriptor) {
408            if (qualifierIsNull.get(descriptor.getOriginal()) != null) {
409                return null;
410            }
411            return qualifiers.get(descriptor.getOriginal());
412        }
413    
414        private final class QualifierGenerator extends Generator<JsNameRef> {
415            public QualifierGenerator() {
416                Rule<JsNameRef> standardObjectsHaveKotlinQualifier = new Rule<JsNameRef>() {
417                    @Override
418                    public JsNameRef apply(@NotNull DeclarationDescriptor descriptor) {
419                        if (!standardClasses.isStandardObject(descriptor)) {
420                            return null;
421                        }
422                        return namer.kotlinObject();
423                    }
424                };
425                //TODO: review and refactor
426                Rule<JsNameRef> packageLevelDeclarationsHaveEnclosingPackagesNamesAsQualifier = new Rule<JsNameRef>() {
427                    @Override
428                    public JsNameRef apply(@NotNull DeclarationDescriptor descriptor) {
429                        DeclarationDescriptor containingDescriptor = getContainingDeclaration(descriptor);
430                        if (!(containingDescriptor instanceof PackageFragmentDescriptor)) {
431                            return null;
432                        }
433    
434                        JsNameRef result = getQualifierForParentPackage(((PackageFragmentDescriptor) containingDescriptor).getFqName());
435    
436                        String moduleName = getExternalModuleName(descriptor);
437                        if (moduleName == null) {
438                            return result;
439                        }
440    
441                        if (LibrarySourcesConfig.UNKNOWN_EXTERNAL_MODULE_NAME.equals(moduleName)) {
442                            return null;
443                        }
444    
445                        JsAstUtils.replaceRootReference(
446                                result, new JsArrayAccess(namer.kotlin("modules"), program.getStringLiteral(moduleName)));
447                        return result;
448                    }
449    
450                    private String getExternalModuleName(DeclarationDescriptor descriptor) {
451                        PsiElement element = BindingContextUtils.descriptorToDeclaration(bindingContext, descriptor);
452                        if (element == null && descriptor instanceof PropertyAccessorDescriptor) {
453                            element = BindingContextUtils.descriptorToDeclaration(bindingContext, ((PropertyAccessorDescriptor) descriptor)
454                                    .getCorrespondingProperty());
455                        }
456    
457                        if (element == null) {
458                            return null;
459                        }
460                        return element.getContainingFile().getUserData(LibrarySourcesConfig.EXTERNAL_MODULE_NAME);
461                    }
462                };
463                Rule<JsNameRef> constructorHaveTheSameQualifierAsTheClass = new Rule<JsNameRef>() {
464                    @Override
465                    public JsNameRef apply(@NotNull DeclarationDescriptor descriptor) {
466                        if (!(descriptor instanceof ConstructorDescriptor)) {
467                            return null;
468                        }
469                        ClassDescriptor containingClass = getContainingClass(descriptor);
470                        assert containingClass != null : "Can't have constructor without a class";
471                        return getQualifierForDescriptor(containingClass);
472                    }
473                };
474                Rule<JsNameRef> libraryObjectsHaveKotlinQualifier = new Rule<JsNameRef>() {
475                    @Override
476                    public JsNameRef apply(@NotNull DeclarationDescriptor descriptor) {
477                        if (isLibraryObject(descriptor)) {
478                            return namer.kotlinObject();
479                        }
480                        return null;
481                    }
482                };
483                addRule(libraryObjectsHaveKotlinQualifier);
484                addRule(constructorHaveTheSameQualifierAsTheClass);
485                addRule(standardObjectsHaveKotlinQualifier);
486                addRule(packageLevelDeclarationsHaveEnclosingPackagesNamesAsQualifier);
487            }
488        }
489    
490        private static class QualifierIsNullGenerator extends Generator<Boolean> {
491    
492            private QualifierIsNullGenerator() {
493                Rule<Boolean> propertiesInClassHaveNoQualifiers = new Rule<Boolean>() {
494                    @Override
495                    public Boolean apply(@NotNull DeclarationDescriptor descriptor) {
496                        if ((descriptor instanceof PropertyDescriptor) && descriptor.getContainingDeclaration() instanceof ClassDescriptor) {
497                            return true;
498                        }
499                        return null;
500                    }
501                };
502                //TODO: hack!  it seems like needed, only for Inheritance from native class
503                Rule<Boolean> nativeObjectsHaveNoQualifiers = new Rule<Boolean>() {
504                    @Override
505                    public Boolean apply(@NotNull DeclarationDescriptor descriptor) {
506                        if (!AnnotationsUtils.isNativeObject(descriptor)) {
507                            return null;
508                        }
509                        return true;
510                    }
511                };
512                addRule(propertiesInClassHaveNoQualifiers);
513                addRule(nativeObjectsHaveNoQualifiers);
514            }
515        }
516    }