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