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