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.psi.PsiElement;
022    import com.intellij.psi.PsiFile;
023    import org.jetbrains.annotations.NotNull;
024    import org.jetbrains.annotations.Nullable;
025    import org.jetbrains.jet.lang.descriptors.*;
026    import org.jetbrains.jet.lang.resolve.BindingContext;
027    import org.jetbrains.jet.lang.resolve.BindingContextUtils;
028    import org.jetbrains.jet.lang.resolve.DescriptorUtils;
029    import org.jetbrains.k2js.config.EcmaVersion;
030    import org.jetbrains.k2js.config.LibrarySourcesConfig;
031    import org.jetbrains.k2js.translate.context.generator.Generator;
032    import org.jetbrains.k2js.translate.context.generator.Rule;
033    import org.jetbrains.k2js.translate.expression.LiteralFunctionTranslator;
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    import org.jetbrains.k2js.translate.utils.JsDescriptorUtils;
038    import org.jetbrains.k2js.translate.utils.PredefinedAnnotation;
039    
040    import java.util.Map;
041    
042    import static org.jetbrains.k2js.translate.utils.AnnotationsUtils.*;
043    import static org.jetbrains.k2js.translate.utils.BindingUtils.isObjectDeclaration;
044    import static org.jetbrains.k2js.translate.utils.JsDescriptorUtils.*;
045    
046    /**
047     * Aggregates all the static parts of the context.
048     */
049    public final class StaticContext {
050    
051        public static StaticContext generateStaticContext(@NotNull BindingContext bindingContext, @NotNull EcmaVersion ecmaVersion) {
052            JsProgram program = new JsProgram("main");
053            Namer namer = Namer.newInstance(program.getRootScope());
054            Intrinsics intrinsics = new Intrinsics();
055            StandardClasses standardClasses = StandardClasses.bindImplementations(namer.getKotlinScope());
056            return new StaticContext(program, bindingContext, namer, intrinsics, standardClasses, program.getRootScope(), ecmaVersion);
057        }
058    
059        @NotNull
060        private final JsProgram program;
061    
062        @NotNull
063        private final BindingContext bindingContext;
064        @NotNull
065        private final Namer namer;
066    
067        @NotNull
068        private final Intrinsics intrinsics;
069    
070        @NotNull
071        private final StandardClasses standardClasses;
072    
073        @NotNull
074        private final JsScope rootScope;
075    
076        @NotNull
077        private final Generator<JsName> names = new NameGenerator();
078        @NotNull
079        private final Generator<JsScope> scopes = new ScopeGenerator();
080        @NotNull
081        private final Generator<JsNameRef> qualifiers = new QualifierGenerator();
082        @NotNull
083        private final Generator<Boolean> qualifierIsNull = new QualifierIsNullGenerator();
084    
085        @NotNull
086        private final Map<JsScope, JsFunction> scopeToFunction = Maps.newHashMap();
087    
088        @NotNull
089        private final EcmaVersion ecmaVersion;
090    
091        @NotNull
092        private final LiteralFunctionTranslator literalFunctionTranslator = new LiteralFunctionTranslator();
093    
094        //TODO: too many parameters in constructor
095        private StaticContext(@NotNull JsProgram program, @NotNull BindingContext bindingContext,
096                @NotNull Namer namer, @NotNull Intrinsics intrinsics,
097                @NotNull StandardClasses standardClasses, @NotNull JsScope rootScope, @NotNull EcmaVersion ecmaVersion) {
098            this.program = program;
099            this.bindingContext = bindingContext;
100            this.namer = namer;
101            this.intrinsics = intrinsics;
102            this.rootScope = rootScope;
103            this.standardClasses = standardClasses;
104            this.ecmaVersion = ecmaVersion;
105        }
106    
107        @NotNull
108        public LiteralFunctionTranslator getLiteralFunctionTranslator() {
109            return literalFunctionTranslator;
110        }
111    
112        public boolean isEcma5() {
113            return ecmaVersion == EcmaVersion.v5;
114        }
115    
116        @NotNull
117        public EcmaVersion getEcmaVersion() {
118            return ecmaVersion;
119        }
120    
121        @NotNull
122        public JsProgram getProgram() {
123            return program;
124        }
125    
126        @NotNull
127        public BindingContext getBindingContext() {
128            return bindingContext;
129        }
130    
131        @NotNull
132        public Intrinsics getIntrinsics() {
133            return intrinsics;
134        }
135    
136        @NotNull
137        public Namer getNamer() {
138            return namer;
139        }
140    
141        @NotNull
142        public JsScope getRootScope() {
143            return rootScope;
144        }
145    
146        @NotNull
147        public JsScope getScopeForDescriptor(@NotNull DeclarationDescriptor descriptor) {
148            JsScope scope = scopes.get(descriptor.getOriginal());
149            assert scope != null : "Must have a scope for descriptor";
150            return scope;
151        }
152    
153        @NotNull
154        public JsFunction getFunctionWithScope(@NotNull CallableDescriptor descriptor) {
155            JsScope scope = getScopeForDescriptor(descriptor);
156            JsFunction function = scopeToFunction.get(scope);
157            assert scope.equals(function.getScope()) : "Inconsistency.";
158            return function;
159        }
160    
161        @NotNull
162        public JsName getNameForDescriptor(@NotNull DeclarationDescriptor descriptor) {
163            JsName name = names.get(descriptor.getOriginal());
164            assert name != null : "Must have name for descriptor";
165            return name;
166        }
167    
168        private final class NameGenerator extends Generator<JsName> {
169            private JsName declareName(DeclarationDescriptor descriptor, String name) {
170                JsScope scope = getEnclosingScope(descriptor);
171                // ecma 5 property name never declares as obfuscatable:
172                // 1) property cannot be overloaded, so, name collision is not possible
173                // 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
174                return isEcma5() ? scope.declareName(name) : scope.declareFreshName(name);
175            }
176    
177            public NameGenerator() {
178                Rule<JsName> namesForStandardClasses = new Rule<JsName>() {
179                    @Override
180                    @Nullable
181                    public JsName apply(@NotNull DeclarationDescriptor data) {
182                        if (!standardClasses.isStandardObject(data)) {
183                            return null;
184                        }
185                        return standardClasses.getStandardObjectName(data);
186                    }
187                };
188                Rule<JsName> namespacesShouldBeDefinedInRootScope = new Rule<JsName>() {
189                    @Override
190                    @Nullable
191                    public JsName apply(@NotNull DeclarationDescriptor descriptor) {
192                        if (!(descriptor instanceof NamespaceDescriptor)) {
193                            return null;
194                        }
195    
196                        String name = Namer.generateNamespaceName(descriptor);
197                        return getRootScope().declareName(name);
198                    }
199                };
200                Rule<JsName> memberDeclarationsInsideParentsScope = new Rule<JsName>() {
201                    @Override
202                    @Nullable
203                    public JsName apply(@NotNull DeclarationDescriptor descriptor) {
204                        JsScope scope = getEnclosingScope(descriptor);
205                        return scope.declareFreshName(descriptor.getName().asString());
206                    }
207                };
208                Rule<JsName> constructorHasTheSameNameAsTheClass = new Rule<JsName>() {
209                    @Override
210                    public JsName apply(@NotNull DeclarationDescriptor descriptor) {
211                        if (!(descriptor instanceof ConstructorDescriptor)) {
212                            return null;
213                        }
214                        ClassDescriptor containingClass = getContainingClass(descriptor);
215                        assert containingClass != null : "Can't have constructor without a class";
216                        return getNameForDescriptor(containingClass);
217                    }
218                };
219                Rule<JsName> accessorsHasNamesWithSpecialPrefixes = new Rule<JsName>() {
220                    @Override
221                    public JsName apply(@NotNull DeclarationDescriptor descriptor) {
222                        if (!(descriptor instanceof PropertyAccessorDescriptor)) {
223                            return null;
224                        }
225    
226                        PropertyAccessorDescriptor accessorDescriptor = (PropertyAccessorDescriptor) descriptor;
227                        String propertyName = accessorDescriptor.getCorrespondingProperty().getName().asString();
228                        if (isObjectDeclaration(bindingContext, accessorDescriptor.getCorrespondingProperty())) {
229                            return declareName(descriptor, propertyName);
230                        }
231    
232                        boolean isGetter = descriptor instanceof PropertyGetterDescriptor;
233                        String accessorName = Namer.getNameForAccessor(propertyName, isGetter,
234                                                                       accessorDescriptor.getReceiverParameter() == null && isEcma5());
235                        return declareName(descriptor, accessorName);
236                    }
237                };
238    
239                Rule<JsName> predefinedObjectsHasUnobfuscatableNames = new Rule<JsName>() {
240                    @Override
241                    public JsName apply(@NotNull DeclarationDescriptor descriptor) {
242                        for (PredefinedAnnotation annotation : PredefinedAnnotation.values()) {
243                            if (!hasAnnotationOrInsideAnnotatedClass(descriptor, annotation)) {
244                                continue;
245                            }
246                            String name = getNameForAnnotatedObject(descriptor, annotation);
247                            name = (name != null) ? name : descriptor.getName().asString();
248                            return getEnclosingScope(descriptor).declareName(name);
249                        }
250                        return null;
251                    }
252                };
253                Rule<JsName> propertiesCorrespondToSpeciallyTreatedBackingFieldNames = new Rule<JsName>() {
254                    @Override
255                    public JsName apply(@NotNull DeclarationDescriptor descriptor) {
256                        if (!(descriptor instanceof PropertyDescriptor)) {
257                            return null;
258                        }
259    
260                        String name = descriptor.getName().asString();
261                        if (!isEcma5() || JsDescriptorUtils.isAsPrivate((PropertyDescriptor) descriptor)) {
262                            name = Namer.getKotlinBackingFieldName(name);
263                        }
264    
265                        return declareName(descriptor, name);
266                    }
267                };
268                //TODO: hack!
269                Rule<JsName> toStringHack = new Rule<JsName>() {
270                    @Override
271                    public JsName apply(@NotNull DeclarationDescriptor descriptor) {
272                        if (!(descriptor instanceof FunctionDescriptor)) {
273                            return null;
274                        }
275                        if (!descriptor.getName().asString().equals("toString")) {
276                            return null;
277                        }
278                        if (((FunctionDescriptor) descriptor).getValueParameters().isEmpty()) {
279                            return getEnclosingScope(descriptor).declareName("toString");
280                        }
281                        return null;
282                    }
283                };
284    
285                Rule<JsName> overridingDescriptorsReferToOriginalName = new Rule<JsName>() {
286                    @Override
287                    public JsName apply(@NotNull DeclarationDescriptor descriptor) {
288                        //TODO: refactor
289                        if (!(descriptor instanceof FunctionDescriptor)) {
290                            return null;
291                        }
292                        FunctionDescriptor overriddenDescriptor = getOverriddenDescriptor((FunctionDescriptor) descriptor);
293                        if (overriddenDescriptor == null) {
294                            return null;
295                        }
296    
297                        JsScope scope = getEnclosingScope(descriptor);
298                        JsName result = getNameForDescriptor(overriddenDescriptor);
299                        scope.declareName(result.getIdent());
300                        return result;
301                    }
302                };
303                addRule(namesForStandardClasses);
304                addRule(constructorHasTheSameNameAsTheClass);
305                addRule(predefinedObjectsHasUnobfuscatableNames);
306                addRule(toStringHack);
307                addRule(propertiesCorrespondToSpeciallyTreatedBackingFieldNames);
308                addRule(namespacesShouldBeDefinedInRootScope);
309                addRule(overridingDescriptorsReferToOriginalName);
310                addRule(accessorsHasNamesWithSpecialPrefixes);
311                addRule(memberDeclarationsInsideParentsScope);
312            }
313        }
314    
315        @NotNull
316        private JsScope getEnclosingScope(@NotNull DeclarationDescriptor descriptor) {
317            DeclarationDescriptor containingDeclaration = getContainingDeclaration(descriptor);
318            return getScopeForDescriptor(containingDeclaration.getOriginal());
319        }
320    
321        private final class ScopeGenerator extends Generator<JsScope> {
322    
323            public ScopeGenerator() {
324                Rule<JsScope> generateNewScopesForClassesWithNoAncestors = new Rule<JsScope>() {
325                    @Override
326                    public JsScope apply(@NotNull DeclarationDescriptor descriptor) {
327                        if (!(descriptor instanceof ClassDescriptor)) {
328                            return null;
329                        }
330                        if (getSuperclass((ClassDescriptor) descriptor) == null) {
331                            return getRootScope().innerScope("Scope for class " + descriptor.getName());
332                        }
333                        return null;
334                    }
335                };
336                Rule<JsScope> generateInnerScopesForDerivedClasses = new Rule<JsScope>() {
337                    @Override
338                    public JsScope apply(@NotNull DeclarationDescriptor descriptor) {
339                        if (!(descriptor instanceof ClassDescriptor)) {
340                            return null;
341                        }
342                        ClassDescriptor superclass = getSuperclass((ClassDescriptor) descriptor);
343                        if (superclass == null) {
344                            return null;
345                        }
346                        return getScopeForDescriptor(superclass).innerScope("Scope for class " + descriptor.getName());
347                    }
348                };
349                Rule<JsScope> generateNewScopesForNamespaceDescriptors = new Rule<JsScope>() {
350                    @Override
351                    public JsScope apply(@NotNull DeclarationDescriptor descriptor) {
352                        if (!(descriptor instanceof NamespaceDescriptor)) {
353                            return null;
354                        }
355                        return getRootScope().innerScope("Namespace " + descriptor.getName());
356                    }
357                };
358                //TODO: never get there
359                Rule<JsScope> generateInnerScopesForMembers = new Rule<JsScope>() {
360                    @Override
361                    public JsScope apply(@NotNull DeclarationDescriptor descriptor) {
362                        JsScope enclosingScope = getEnclosingScope(descriptor);
363                        return enclosingScope.innerScope("Scope for member " + descriptor.getName());
364                    }
365                };
366                Rule<JsScope> createFunctionObjectsForCallableDescriptors = new Rule<JsScope>() {
367                    @Override
368                    public JsScope apply(@NotNull DeclarationDescriptor descriptor) {
369                        if (!(descriptor instanceof CallableDescriptor)) {
370                            return null;
371                        }
372                        JsScope enclosingScope = getEnclosingScope(descriptor);
373    
374                        JsFunction correspondingFunction = JsAstUtils.createFunctionWithEmptyBody(enclosingScope);
375                        assert (!scopeToFunction.containsKey(correspondingFunction.getScope())) : "Scope to function value overridden for " + descriptor;
376                        scopeToFunction.put(correspondingFunction.getScope(), correspondingFunction);
377                        return correspondingFunction.getScope();
378                    }
379                };
380                addRule(createFunctionObjectsForCallableDescriptors);
381                addRule(generateNewScopesForClassesWithNoAncestors);
382                addRule(generateInnerScopesForDerivedClasses);
383                addRule(generateNewScopesForNamespaceDescriptors);
384                addRule(generateInnerScopesForMembers);
385            }
386        }
387    
388        @Nullable
389        public JsNameRef getQualifierForDescriptor(@NotNull DeclarationDescriptor descriptor) {
390            if (qualifierIsNull.get(descriptor.getOriginal()) != null) {
391                return null;
392            }
393            return qualifiers.get(descriptor.getOriginal());
394        }
395    
396        private final class QualifierGenerator extends Generator<JsNameRef> {
397            public QualifierGenerator() {
398                Rule<JsNameRef> standardObjectsHaveKotlinQualifier = new Rule<JsNameRef>() {
399                    @Override
400                    public JsNameRef apply(@NotNull DeclarationDescriptor descriptor) {
401                        if (!standardClasses.isStandardObject(descriptor)) {
402                            return null;
403                        }
404                        return namer.kotlinObject();
405                    }
406                };
407                //TODO: review and refactor
408                Rule<JsNameRef> namespaceLevelDeclarationsHaveEnclosingNamespacesNamesAsQualifier = new Rule<JsNameRef>() {
409                    @Override
410                    public JsNameRef apply(@NotNull DeclarationDescriptor descriptor) {
411                        DeclarationDescriptor containingDescriptor = getContainingDeclaration(descriptor);
412                        if (!(containingDescriptor instanceof NamespaceDescriptor)) {
413                            return null;
414                        }
415    
416                        JsNameRef result = new JsNameRef(getNameForDescriptor(containingDescriptor));
417                        if (DescriptorUtils.isRootNamespace((NamespaceDescriptor) containingDescriptor)) {
418                            return result;
419                        }
420    
421                        JsNameRef qualifier = result;
422                        while ((containingDescriptor = getContainingDeclaration(containingDescriptor)) instanceof NamespaceDescriptor &&
423                               !DescriptorUtils.isRootNamespace((NamespaceDescriptor) containingDescriptor)) {
424                            JsNameRef ref = getNameForDescriptor(containingDescriptor).makeRef();
425                            qualifier.setQualifier(ref);
426                            qualifier = ref;
427                        }
428    
429                        PsiElement element = BindingContextUtils.descriptorToDeclaration(bindingContext, descriptor);
430                        if (element == null && descriptor instanceof PropertyAccessorDescriptor) {
431                            element = BindingContextUtils.descriptorToDeclaration(bindingContext, ((PropertyAccessorDescriptor) descriptor)
432                                    .getCorrespondingProperty());
433                        }
434    
435                        if (element != null) {
436                            PsiFile file = element.getContainingFile();
437                            String moduleName = file.getUserData(LibrarySourcesConfig.EXTERNAL_MODULE_NAME);
438                            if (LibrarySourcesConfig.UNKNOWN_EXTERNAL_MODULE_NAME.equals(moduleName)) {
439                                return null;
440                            }
441                            else if (moduleName != null) {
442                                qualifier.setQualifier(new JsArrayAccess(namer.kotlin("modules"), program.getStringLiteral(moduleName)));
443                            }
444                        }
445    
446                        if (qualifier.getQualifier() == null) {
447                            qualifier.setQualifier(new JsNameRef(Namer.getRootNamespaceName()));
448                        }
449    
450                        return result;
451                    }
452                };
453                Rule<JsNameRef> constructorHaveTheSameQualifierAsTheClass = new Rule<JsNameRef>() {
454                    @Override
455                    public JsNameRef apply(@NotNull DeclarationDescriptor descriptor) {
456                        if (!(descriptor instanceof ConstructorDescriptor)) {
457                            return null;
458                        }
459                        ClassDescriptor containingClass = getContainingClass(descriptor);
460                        assert containingClass != null : "Can't have constructor without a class";
461                        return getQualifierForDescriptor(containingClass);
462                    }
463                };
464                Rule<JsNameRef> libraryObjectsHaveKotlinQualifier = new Rule<JsNameRef>() {
465                    @Override
466                    public JsNameRef apply(@NotNull DeclarationDescriptor descriptor) {
467                        if (isLibraryObject(descriptor)) {
468                            return namer.kotlinObject();
469                        }
470                        return null;
471                    }
472                };
473                addRule(libraryObjectsHaveKotlinQualifier);
474                addRule(constructorHaveTheSameQualifierAsTheClass);
475                addRule(standardObjectsHaveKotlinQualifier);
476                addRule(namespaceLevelDeclarationsHaveEnclosingNamespacesNamesAsQualifier);
477            }
478        }
479    
480        private static class QualifierIsNullGenerator extends Generator<Boolean> {
481    
482            private QualifierIsNullGenerator() {
483                Rule<Boolean> propertiesHaveNoQualifiers = new Rule<Boolean>() {
484                    @Override
485                    public Boolean apply(@NotNull DeclarationDescriptor descriptor) {
486                        if (!(descriptor instanceof PropertyDescriptor)) {
487                            return null;
488                        }
489                        return true;
490                    }
491                };
492                //TODO: hack!
493                Rule<Boolean> nativeObjectsHaveNoQualifiers = new Rule<Boolean>() {
494                    @Override
495                    public Boolean apply(@NotNull DeclarationDescriptor descriptor) {
496                        if (!AnnotationsUtils.isNativeObject(descriptor)) {
497                            return null;
498                        }
499                        return true;
500                    }
501                };
502                Rule<Boolean> topLevelNamespaceHaveNoQualifier = new Rule<Boolean>() {
503                    @Override
504                    public Boolean apply(@NotNull DeclarationDescriptor descriptor) {
505                        if (descriptor instanceof NamespaceDescriptor && DescriptorUtils.isRootNamespace((NamespaceDescriptor) descriptor)) {
506                            return true;
507                        }
508                        return null;
509                    }
510                };
511                addRule(topLevelNamespaceHaveNoQualifier);
512                addRule(propertiesHaveNoQualifiers);
513                addRule(nativeObjectsHaveNoQualifiers);
514            }
515        }
516    }