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.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.jet.lang.resolve.DescriptorToSourceUtils.descriptorToDeclaration;
041    import static org.jetbrains.k2js.translate.utils.AnnotationsUtils.getNameForAnnotatedObjectWithOverrides;
042    import static org.jetbrains.k2js.translate.utils.AnnotationsUtils.isLibraryObject;
043    import static org.jetbrains.k2js.translate.utils.JsDescriptorUtils.*;
044    import static org.jetbrains.k2js.translate.utils.TranslationUtils.getMangledName;
045    import static org.jetbrains.k2js.translate.utils.TranslationUtils.getSuggestedName;
046    
047    /**
048     * Aggregates all the static parts of the context.
049     */
050    public final class StaticContext {
051    
052        public static StaticContext generateStaticContext(@NotNull BindingContext bindingContext, @NotNull EcmaVersion ecmaVersion, @NotNull ModuleDescriptor moduleDescriptor) {
053            JsProgram program = new JsProgram("main");
054            Namer namer = Namer.newInstance(program.getRootScope());
055            Intrinsics intrinsics = new Intrinsics();
056            StandardClasses standardClasses = StandardClasses.bindImplementations(namer.getKotlinScope());
057            return new StaticContext(program, bindingContext, namer, intrinsics, standardClasses, program.getRootScope(), ecmaVersion, moduleDescriptor);
058        }
059    
060        @NotNull
061        private final JsProgram program;
062    
063        @NotNull
064        private final BindingContext bindingContext;
065        @NotNull
066        private final Namer namer;
067    
068        @NotNull
069        private final Intrinsics intrinsics;
070    
071        @NotNull
072        private final StandardClasses standardClasses;
073    
074        @NotNull
075        private final ReflectionTypes reflectionTypes;
076    
077        @NotNull
078        private final JsScope rootScope;
079    
080        @NotNull
081        private final Generator<JsName> names = new NameGenerator();
082        @NotNull
083        private final Map<FqName, JsName> packageNames = Maps.newHashMap();
084        @NotNull
085        private final Generator<JsScope> scopes = new ScopeGenerator();
086        @NotNull
087        private final Generator<JsNameRef> qualifiers = new QualifierGenerator();
088        @NotNull
089        private final Generator<Boolean> qualifierIsNull = new QualifierIsNullGenerator();
090    
091        @NotNull
092        private final Map<JsScope, JsFunction> scopeToFunction = Maps.newHashMap();
093    
094        @NotNull
095        private final EcmaVersion ecmaVersion;
096    
097        //TODO: too many parameters in constructor
098        private StaticContext(@NotNull JsProgram program, @NotNull BindingContext bindingContext,
099                @NotNull Namer namer, @NotNull Intrinsics intrinsics,
100                @NotNull StandardClasses standardClasses, @NotNull JsScope rootScope, @NotNull EcmaVersion ecmaVersion, @NotNull ModuleDescriptor moduleDescriptor) {
101            this.program = program;
102            this.bindingContext = bindingContext;
103            this.namer = namer;
104            this.intrinsics = intrinsics;
105            this.rootScope = rootScope;
106            this.standardClasses = standardClasses;
107            this.ecmaVersion = ecmaVersion;
108            this.reflectionTypes = new ReflectionTypes(moduleDescriptor);
109        }
110    
111        public boolean isEcma5() {
112            return ecmaVersion == EcmaVersion.v5;
113        }
114    
115        @NotNull
116        public JsProgram getProgram() {
117            return program;
118        }
119    
120        @NotNull
121        public BindingContext getBindingContext() {
122            return bindingContext;
123        }
124    
125        @NotNull
126        public Intrinsics getIntrinsics() {
127            return intrinsics;
128        }
129    
130        @NotNull
131        public Namer getNamer() {
132            return namer;
133        }
134    
135        @NotNull
136        public ReflectionTypes getReflectionTypes() {
137            return reflectionTypes;
138        }
139    
140        @NotNull
141        public JsScope getRootScope() {
142            return rootScope;
143        }
144    
145        @NotNull
146        public JsScope getScopeForDescriptor(@NotNull DeclarationDescriptor descriptor) {
147            JsScope scope = scopes.get(descriptor.getOriginal());
148            assert scope != null : "Must have a scope for descriptor";
149            return scope;
150        }
151    
152        @NotNull
153        public JsFunction getFunctionWithScope(@NotNull CallableDescriptor descriptor) {
154            JsScope scope = getScopeForDescriptor(descriptor);
155            JsFunction function = scopeToFunction.get(scope);
156            assert scope.equals(function.getScope()) : "Inconsistency.";
157            return function;
158        }
159    
160        @NotNull
161        public JsNameRef getQualifiedReference(@NotNull DeclarationDescriptor descriptor) {
162            if (descriptor instanceof PackageViewDescriptor) {
163                return getQualifiedReference(((PackageViewDescriptor) descriptor).getFqName());
164            }
165            if (descriptor instanceof PackageFragmentDescriptor) {
166                return getQualifiedReference(((PackageFragmentDescriptor) descriptor).getFqName());
167            }
168    
169            return new JsNameRef(getNameForDescriptor(descriptor), getQualifierForDescriptor(descriptor));
170        }
171    
172        @NotNull
173        public JsNameRef getQualifiedReference(@NotNull FqName packageFqName) {
174            return new JsNameRef(getNameForPackage(packageFqName),
175                                 packageFqName.isRoot() ? null : getQualifierForParentPackage(packageFqName.parent()));
176        }
177    
178        @NotNull
179        public JsName getNameForDescriptor(@NotNull DeclarationDescriptor descriptor) {
180            JsName name = names.get(descriptor.getOriginal());
181            assert name != null : "Must have name for descriptor";
182            return name;
183        }
184    
185        @NotNull
186        public JsName getNameForPackage(@NotNull final FqName packageFqName) {
187            return ContainerUtil.getOrCreate(packageNames, packageFqName, new Factory<JsName>() {
188                @Override
189                public JsName create() {
190                    String name = Namer.generatePackageName(packageFqName);
191                    return getRootScope().declareName(name);
192                }
193            });
194        }
195    
196        @NotNull
197        private JsNameRef getQualifierForParentPackage(@NotNull FqName packageFqName) {
198            JsNameRef result = null;
199            JsNameRef qualifier = null;
200    
201            for (FqName pathElement : ContainerUtil.reverse(packageFqName.path())) {
202                JsNameRef ref = getNameForPackage(pathElement).makeRef();
203    
204                if (qualifier == null) {
205                    result = ref;
206                }
207                else {
208                    qualifier.setQualifier(ref);
209                }
210    
211                qualifier = ref;
212            }
213    
214            assert result != null : "didn't iterate: " + packageFqName;
215            return result;
216        }
217    
218        private final class NameGenerator extends Generator<JsName> {
219    
220            public NameGenerator() {
221                Rule<JsName> namesForStandardClasses = new Rule<JsName>() {
222                    @Override
223                    @Nullable
224                    public JsName apply(@NotNull DeclarationDescriptor data) {
225                        if (!standardClasses.isStandardObject(data)) {
226                            return null;
227                        }
228                        return standardClasses.getStandardObjectName(data);
229                    }
230                };
231                Rule<JsName> memberDeclarationsInsideParentsScope = new Rule<JsName>() {
232                    @Override
233                    @Nullable
234                    public JsName apply(@NotNull DeclarationDescriptor descriptor) {
235                        JsScope scope = getEnclosingScope(descriptor);
236                        return scope.declareFreshName(getSuggestedName(descriptor));
237                    }
238                };
239                Rule<JsName> constructorHasTheSameNameAsTheClass = new Rule<JsName>() {
240                    @Override
241                    public JsName apply(@NotNull DeclarationDescriptor descriptor) {
242                        if (!(descriptor instanceof ConstructorDescriptor)) {
243                            return null;
244                        }
245                        ClassDescriptor containingClass = getContainingClass(descriptor);
246                        assert containingClass != null : "Can't have constructor without a class";
247                        return getNameForDescriptor(containingClass);
248                    }
249                };
250    
251                // ecma 5 property name never declares as obfuscatable:
252                // 1) property cannot be overloaded, so, name collision is not possible
253                // 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
254                //
255                // But extension property may obfuscatable, because transform into function. Example: String.foo = 1, Int.foo = 2
256                Rule<JsName> propertyOrPropertyAccessor = new Rule<JsName>() {
257                    @Override
258                    public JsName apply(@NotNull DeclarationDescriptor descriptor) {
259                        PropertyDescriptor propertyDescriptor;
260                        if (descriptor instanceof PropertyAccessorDescriptor) {
261                            propertyDescriptor = ((PropertyAccessorDescriptor) descriptor).getCorrespondingProperty();
262                        }
263                        else if (descriptor instanceof PropertyDescriptor) {
264                            propertyDescriptor = (PropertyDescriptor) descriptor;
265                        }
266                        else {
267                            return null;
268                        }
269    
270                        String nameFromAnnotation = getNameForAnnotatedObjectWithOverrides(propertyDescriptor);
271                        if (nameFromAnnotation != null) {
272                            return declarePropertyOrPropertyAccessorName(descriptor, nameFromAnnotation, false);
273                        }
274    
275                        String propertyName =  propertyDescriptor.getName().asString();
276    
277                        if (!isExtension(propertyDescriptor)) {
278                            if (propertyDescriptor.getVisibility() == Visibilities.PRIVATE) {
279                                propertyName = getMangledName(propertyDescriptor, propertyName);
280                            }
281                            return declarePropertyOrPropertyAccessorName(descriptor, propertyName, false);
282                        } else {
283                            if (descriptor instanceof PropertyDescriptor) {
284                                return declarePropertyOrPropertyAccessorName(descriptor, propertyName, true);
285                            } else {
286                                String propertyJsName = getNameForDescriptor(propertyDescriptor).getIdent();
287                                boolean isGetter = descriptor instanceof PropertyGetterDescriptor;
288                                String accessorName = Namer.getNameForAccessor(propertyJsName, isGetter, false);
289                                return declarePropertyOrPropertyAccessorName(descriptor, accessorName, false);
290                            }
291                        }
292                    }
293                };
294    
295                Rule<JsName> predefinedObjectsHasUnobfuscatableNames = new Rule<JsName>() {
296                    @Override
297                    public JsName apply(@NotNull DeclarationDescriptor descriptor) {
298                        // The mixing of override and rename by annotation(e.g. native) is forbidden.
299                        if (descriptor instanceof CallableMemberDescriptor &&
300                            !((CallableMemberDescriptor) descriptor).getOverriddenDescriptors().isEmpty()) {
301                            return null;
302                        }
303    
304                        String name = getNameForAnnotatedObjectWithOverrides(descriptor);
305                        if (name != null) return getEnclosingScope(descriptor).declareName(name);
306                        return null;
307                    }
308                };
309    
310                Rule<JsName> overridingDescriptorsReferToOriginalName = new Rule<JsName>() {
311                    @Override
312                    public JsName apply(@NotNull DeclarationDescriptor descriptor) {
313                        //TODO: refactor
314                        if (!(descriptor instanceof FunctionDescriptor)) {
315                            return null;
316                        }
317                        FunctionDescriptor overriddenDescriptor = getOverriddenDescriptor((FunctionDescriptor) descriptor);
318                        if (overriddenDescriptor == null) {
319                            return null;
320                        }
321    
322                        JsScope scope = getEnclosingScope(descriptor);
323                        JsName result = getNameForDescriptor(overriddenDescriptor);
324                        scope.declareName(result.getIdent());
325                        return result;
326                    }
327                };
328                addRule(namesForStandardClasses);
329                addRule(constructorHasTheSameNameAsTheClass);
330                addRule(propertyOrPropertyAccessor);
331                addRule(predefinedObjectsHasUnobfuscatableNames);
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> generateNewScopesForPackageDescriptors = new Rule<JsScope>() {
378                    @Override
379                    public JsScope apply(@NotNull DeclarationDescriptor descriptor) {
380                        if (!(descriptor instanceof PackageFragmentDescriptor)) {
381                            return null;
382                        }
383                        return getRootScope().innerScope("Package " + 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(generateNewScopesForPackageDescriptors);
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> packageLevelDeclarationsHaveEnclosingPackagesNamesAsQualifier = new Rule<JsNameRef>() {
437                    @Override
438                    public JsNameRef apply(@NotNull DeclarationDescriptor descriptor) {
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> constructorHaveTheSameQualifierAsTheClass = new Rule<JsNameRef>() {
473                    @Override
474                    public JsNameRef apply(@NotNull DeclarationDescriptor descriptor) {
475                        if (!(descriptor instanceof ConstructorDescriptor)) {
476                            return null;
477                        }
478                        ClassDescriptor containingClass = getContainingClass(descriptor);
479                        assert containingClass != null : "Can't have constructor without a class";
480                        return getQualifierForDescriptor(containingClass);
481                    }
482                };
483                Rule<JsNameRef> libraryObjectsHaveKotlinQualifier = new Rule<JsNameRef>() {
484                    @Override
485                    public JsNameRef apply(@NotNull DeclarationDescriptor descriptor) {
486                        if (isLibraryObject(descriptor)) {
487                            return namer.kotlinObject();
488                        }
489                        return null;
490                    }
491                };
492                addRule(libraryObjectsHaveKotlinQualifier);
493                addRule(constructorHaveTheSameQualifierAsTheClass);
494                addRule(standardObjectsHaveKotlinQualifier);
495                addRule(packageLevelDeclarationsHaveEnclosingPackagesNamesAsQualifier);
496            }
497        }
498    
499        private static class QualifierIsNullGenerator extends Generator<Boolean> {
500    
501            private QualifierIsNullGenerator() {
502                Rule<Boolean> propertiesInClassHaveNoQualifiers = new Rule<Boolean>() {
503                    @Override
504                    public Boolean apply(@NotNull DeclarationDescriptor descriptor) {
505                        if ((descriptor instanceof PropertyDescriptor) && descriptor.getContainingDeclaration() instanceof ClassDescriptor) {
506                            return true;
507                        }
508                        return null;
509                    }
510                };
511                //TODO: hack!  it seems like needed, only for Inheritance from native class
512                Rule<Boolean> nativeObjectsHaveNoQualifiers = new Rule<Boolean>() {
513                    @Override
514                    public Boolean apply(@NotNull DeclarationDescriptor descriptor) {
515                        if (!AnnotationsUtils.isNativeObject(descriptor)) {
516                            return null;
517                        }
518                        return true;
519                    }
520                };
521                addRule(propertiesInClassHaveNoQualifiers);
522                addRule(nativeObjectsHaveNoQualifiers);
523            }
524        }
525    }