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