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
017package org.jetbrains.k2js.translate.context;
018
019import com.google.common.collect.Maps;
020import com.google.dart.compiler.backend.js.ast.*;
021import com.intellij.psi.PsiElement;
022import com.intellij.psi.PsiFile;
023import org.jetbrains.annotations.NotNull;
024import org.jetbrains.annotations.Nullable;
025import org.jetbrains.jet.lang.descriptors.*;
026import org.jetbrains.jet.lang.resolve.BindingContext;
027import org.jetbrains.jet.lang.resolve.BindingContextUtils;
028import org.jetbrains.jet.lang.resolve.DescriptorUtils;
029import org.jetbrains.k2js.config.EcmaVersion;
030import org.jetbrains.k2js.config.LibrarySourcesConfig;
031import org.jetbrains.k2js.translate.context.generator.Generator;
032import org.jetbrains.k2js.translate.context.generator.Rule;
033import org.jetbrains.k2js.translate.expression.LiteralFunctionTranslator;
034import org.jetbrains.k2js.translate.intrinsic.Intrinsics;
035import org.jetbrains.k2js.translate.utils.AnnotationsUtils;
036import org.jetbrains.k2js.translate.utils.JsAstUtils;
037import org.jetbrains.k2js.translate.utils.JsDescriptorUtils;
038import org.jetbrains.k2js.translate.utils.PredefinedAnnotation;
039
040import java.util.Map;
041
042import static org.jetbrains.k2js.translate.utils.AnnotationsUtils.*;
043import static org.jetbrains.k2js.translate.utils.BindingUtils.isObjectDeclaration;
044import static org.jetbrains.k2js.translate.utils.JsDescriptorUtils.*;
045
046/**
047 * Aggregates all the static parts of the context.
048 */
049public 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                        else if (result == qualifier && result.getIdent().equals("kotlin")) {
445                            // todo WebDemoExamples2Test#testBuilder, package "kotlin" from kotlin/js/js.libraries/src/stdlib/JUMaps.kt must be inlined
446                          //  return qualifier;
447                        }
448                    }
449
450                    if (qualifier.getQualifier() == null) {
451                        qualifier.setQualifier(new JsNameRef(Namer.getRootNamespaceName()));
452                    }
453
454                    return result;
455                }
456            };
457            Rule<JsNameRef> constructorHaveTheSameQualifierAsTheClass = new Rule<JsNameRef>() {
458                @Override
459                public JsNameRef apply(@NotNull DeclarationDescriptor descriptor) {
460                    if (!(descriptor instanceof ConstructorDescriptor)) {
461                        return null;
462                    }
463                    ClassDescriptor containingClass = getContainingClass(descriptor);
464                    assert containingClass != null : "Can't have constructor without a class";
465                    return getQualifierForDescriptor(containingClass);
466                }
467            };
468            Rule<JsNameRef> libraryObjectsHaveKotlinQualifier = new Rule<JsNameRef>() {
469                @Override
470                public JsNameRef apply(@NotNull DeclarationDescriptor descriptor) {
471                    if (isLibraryObject(descriptor)) {
472                        return namer.kotlinObject();
473                    }
474                    return null;
475                }
476            };
477            addRule(libraryObjectsHaveKotlinQualifier);
478            addRule(constructorHaveTheSameQualifierAsTheClass);
479            addRule(standardObjectsHaveKotlinQualifier);
480            addRule(namespaceLevelDeclarationsHaveEnclosingNamespacesNamesAsQualifier);
481        }
482    }
483
484    private static class QualifierIsNullGenerator extends Generator<Boolean> {
485
486        private QualifierIsNullGenerator() {
487            Rule<Boolean> propertiesHaveNoQualifiers = new Rule<Boolean>() {
488                @Override
489                public Boolean apply(@NotNull DeclarationDescriptor descriptor) {
490                    if (!(descriptor instanceof PropertyDescriptor)) {
491                        return null;
492                    }
493                    return true;
494                }
495            };
496            //TODO: hack!
497            Rule<Boolean> nativeObjectsHaveNoQualifiers = new Rule<Boolean>() {
498                @Override
499                public Boolean apply(@NotNull DeclarationDescriptor descriptor) {
500                    if (!AnnotationsUtils.isNativeObject(descriptor)) {
501                        return null;
502                    }
503                    return true;
504                }
505            };
506            Rule<Boolean> topLevelNamespaceHaveNoQualifier = new Rule<Boolean>() {
507                @Override
508                public Boolean apply(@NotNull DeclarationDescriptor descriptor) {
509                    if (descriptor instanceof NamespaceDescriptor && DescriptorUtils.isRootNamespace((NamespaceDescriptor) descriptor)) {
510                        return true;
511                    }
512                    return null;
513                }
514            };
515            addRule(topLevelNamespaceHaveNoQualifier);
516            addRule(propertiesHaveNoQualifiers);
517            addRule(nativeObjectsHaveNoQualifiers);
518        }
519    }
520}