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