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