001    /*
002     * Copyright 2010-2016 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.dart.compiler.backend.js.ast.*;
020    import com.intellij.psi.PsiElement;
021    import org.jetbrains.annotations.NotNull;
022    import org.jetbrains.annotations.Nullable;
023    import org.jetbrains.kotlin.builtins.ReflectionTypes;
024    import org.jetbrains.kotlin.descriptors.*;
025    import org.jetbrains.kotlin.js.config.JsConfig;
026    import org.jetbrains.kotlin.js.translate.intrinsic.Intrinsics;
027    import org.jetbrains.kotlin.js.translate.utils.TranslationUtils;
028    import org.jetbrains.kotlin.name.FqName;
029    import org.jetbrains.kotlin.psi.KtExpression;
030    import org.jetbrains.kotlin.resolve.BindingContext;
031    import org.jetbrains.kotlin.resolve.BindingTrace;
032    import org.jetbrains.kotlin.resolve.DescriptorUtils;
033    import org.jetbrains.kotlin.resolve.scopes.receivers.ExtensionReceiver;
034    
035    import java.util.*;
036    
037    import static org.jetbrains.kotlin.js.translate.context.UsageTrackerKt.getNameForCapturedDescriptor;
038    import static org.jetbrains.kotlin.js.translate.utils.BindingUtils.getDescriptorForElement;
039    
040    /**
041     * All the info about the state of the translation process.
042     */
043    public class TranslationContext {
044        @NotNull
045        private final DynamicContext dynamicContext;
046        @NotNull
047        private final StaticContext staticContext;
048        @NotNull
049        private final AliasingContext aliasingContext;
050        @Nullable
051        private final UsageTracker usageTracker;
052        @Nullable
053        private final TranslationContext parent;
054        @Nullable
055        private final DefinitionPlace definitionPlace;
056        @Nullable
057        private final DeclarationDescriptor declarationDescriptor;
058        @Nullable
059        private final ClassDescriptor classDescriptor;
060    
061        @NotNull
062        public static TranslationContext rootContext(@NotNull StaticContext staticContext, JsFunction rootFunction) {
063            DynamicContext rootDynamicContext = DynamicContext.rootContext(rootFunction.getScope(), rootFunction.getBody());
064            AliasingContext rootAliasingContext = AliasingContext.getCleanContext();
065            return new TranslationContext(null, staticContext, rootDynamicContext, rootAliasingContext, null, null, null);
066        }
067    
068        private final Map<JsExpression, TemporaryConstVariable> expressionToTempConstVariableCache = new HashMap<JsExpression, TemporaryConstVariable>();
069    
070        private TranslationContext(
071                @Nullable TranslationContext parent,
072                @NotNull StaticContext staticContext,
073                @NotNull DynamicContext dynamicContext,
074                @NotNull AliasingContext aliasingContext,
075                @Nullable UsageTracker usageTracker,
076                @Nullable DefinitionPlace definitionPlace,
077                @Nullable DeclarationDescriptor declarationDescriptor
078        ) {
079            this.parent = parent;
080            this.dynamicContext = dynamicContext;
081            this.staticContext = staticContext;
082            this.aliasingContext = aliasingContext;
083            this.usageTracker = usageTracker;
084            this.definitionPlace = definitionPlace;
085            this.declarationDescriptor = declarationDescriptor;
086            if (declarationDescriptor instanceof ClassDescriptor) {
087                this.classDescriptor = (ClassDescriptor) declarationDescriptor;
088            }
089            else {
090                this.classDescriptor = parent != null ? parent.classDescriptor : null;
091            }
092        }
093    
094        @Nullable
095        public UsageTracker usageTracker() {
096            return usageTracker;
097        }
098    
099        @NotNull
100        public DynamicContext dynamicContext() {
101            return dynamicContext;
102        }
103    
104        @NotNull
105        public TranslationContext contextWithScope(@NotNull JsFunction fun) {
106            return this.newFunctionBody(fun, aliasingContext, declarationDescriptor);
107        }
108    
109        @NotNull
110        public TranslationContext newFunctionBody(@NotNull JsFunction fun, @Nullable AliasingContext aliasingContext,
111                DeclarationDescriptor descriptor) {
112            DynamicContext dynamicContext = DynamicContext.newContext(fun.getScope(), fun.getBody());
113            if (aliasingContext == null) {
114                aliasingContext = this.aliasingContext.inner();
115            }
116    
117            return new TranslationContext(this, this.staticContext, dynamicContext, aliasingContext, this.usageTracker, null, descriptor);
118        }
119    
120        @NotNull
121        public TranslationContext newFunctionBodyWithUsageTracker(@NotNull JsFunction fun, @NotNull MemberDescriptor descriptor) {
122            DynamicContext dynamicContext = DynamicContext.newContext(fun.getScope(), fun.getBody());
123            UsageTracker usageTracker = new UsageTracker(this.usageTracker, descriptor, fun.getScope());
124            return new TranslationContext(this, this.staticContext, dynamicContext, this.aliasingContext.inner(), usageTracker,
125                                          this.definitionPlace, descriptor);
126        }
127    
128        @NotNull
129        public TranslationContext innerWithUsageTracker(@NotNull JsScope scope, @NotNull MemberDescriptor descriptor) {
130            UsageTracker usageTracker = new UsageTracker(this.usageTracker, descriptor, scope);
131            return new TranslationContext(this, staticContext, dynamicContext, aliasingContext.inner(), usageTracker, definitionPlace,
132                                          descriptor);
133        }
134    
135        @NotNull
136        public TranslationContext innerBlock(@NotNull JsBlock block) {
137            return new TranslationContext(this, staticContext, dynamicContext.innerBlock(block), aliasingContext, usageTracker, null,
138                                          this.declarationDescriptor);
139        }
140    
141        @NotNull
142        public TranslationContext innerBlock() {
143            return innerBlock(new JsBlock());
144        }
145    
146        @NotNull
147        public TranslationContext newDeclaration(@NotNull DeclarationDescriptor descriptor, @Nullable DefinitionPlace place) {
148            DynamicContext dynamicContext = DynamicContext.newContext(getScopeForDescriptor(descriptor), getBlockForDescriptor(descriptor));
149            return new TranslationContext(this, staticContext, dynamicContext, aliasingContext, usageTracker, place, descriptor);
150        }
151    
152        @NotNull
153        private TranslationContext innerWithAliasingContext(AliasingContext aliasingContext) {
154            return new TranslationContext(this, staticContext, dynamicContext, aliasingContext, usageTracker, null, declarationDescriptor);
155        }
156    
157        @NotNull
158        public TranslationContext innerContextWithAliased(@NotNull DeclarationDescriptor correspondingDescriptor, @NotNull JsExpression alias) {
159            return this.innerWithAliasingContext(aliasingContext.inner(correspondingDescriptor, alias));
160        }
161    
162        @NotNull
163        public TranslationContext innerContextWithAliasesForExpressions(@NotNull Map<KtExpression, JsExpression> aliases) {
164            return this.innerWithAliasingContext(aliasingContext.withExpressionsAliased(aliases));
165        }
166    
167        @NotNull
168        public TranslationContext innerContextWithDescriptorsAliased(@NotNull Map<DeclarationDescriptor, JsExpression> aliases) {
169            return this.innerWithAliasingContext(aliasingContext.withDescriptorsAliased(aliases));
170        }
171    
172        @NotNull
173        private JsBlock getBlockForDescriptor(@NotNull DeclarationDescriptor descriptor) {
174            if (descriptor instanceof CallableDescriptor) {
175                return getFunctionObject((CallableDescriptor) descriptor).getBody();
176            }
177            else {
178                return new JsBlock();
179            }
180        }
181    
182        @NotNull
183        public BindingContext bindingContext() {
184            return staticContext.getBindingContext();
185        }
186    
187        @NotNull
188        public BindingTrace bindingTrace() {
189            return staticContext.getBindingTrace();
190        }
191    
192        @NotNull
193        public JsScope getScopeForDescriptor(@NotNull DeclarationDescriptor descriptor) {
194            return staticContext.getScopeForDescriptor(descriptor);
195        }
196    
197        @NotNull
198        public JsName getNameForElement(@NotNull PsiElement element) {
199            DeclarationDescriptor descriptor = getDescriptorForElement(bindingContext(), element);
200            return getNameForDescriptor(descriptor);
201        }
202    
203        @NotNull
204        public JsName getNameForDescriptor(@NotNull DeclarationDescriptor descriptor) {
205            return staticContext.getNameForDescriptor(descriptor);
206        }
207    
208        @NotNull
209        public JsName getNameForPackage(@NotNull FqName fqName) {
210            return staticContext.getNameForPackage(fqName);
211        }
212    
213        @NotNull
214        public JsName declarePropertyOrPropertyAccessorName(@NotNull DeclarationDescriptor descriptor, @NotNull String name, boolean fresh) {
215            return staticContext.declarePropertyOrPropertyAccessorName(descriptor, name, fresh);
216        }
217    
218        @NotNull
219        public JsNameRef getQualifiedReference(@NotNull DeclarationDescriptor descriptor) {
220            return staticContext.getQualifiedReference(descriptor);
221        }
222    
223        @NotNull
224        public JsNameRef getQualifiedReference(@NotNull FqName packageFqName) {
225            return staticContext.getQualifiedReference(packageFqName);
226        }
227    
228        @Nullable
229        public JsExpression getQualifierForDescriptor(@NotNull DeclarationDescriptor descriptor) {
230            return staticContext.getQualifierForDescriptor(descriptor);
231        }
232    
233        @NotNull
234        public TemporaryVariable declareTemporary(@Nullable JsExpression initExpression) {
235            return dynamicContext.declareTemporary(initExpression);
236        }
237    
238        @NotNull
239        public TemporaryConstVariable getOrDeclareTemporaryConstVariable(@NotNull JsExpression expression) {
240            TemporaryConstVariable tempVar = expressionToTempConstVariableCache.get(expression);
241    
242            if (tempVar == null) {
243                TemporaryVariable tmpVar = declareTemporary(expression);
244    
245                tempVar = new TemporaryConstVariable(tmpVar.name(), tmpVar.assignmentExpression());
246    
247                expressionToTempConstVariableCache.put(expression, tempVar);
248                expressionToTempConstVariableCache.put(tmpVar.assignmentExpression(), tempVar);
249            }
250    
251            return tempVar;
252        }
253    
254        public void associateExpressionToLazyValue(JsExpression expression, TemporaryConstVariable temporaryConstVariable) {
255            assert expression == temporaryConstVariable.assignmentExpression();
256            expressionToTempConstVariableCache.put(expression, temporaryConstVariable);
257        }
258    
259        @NotNull
260        public Namer namer() {
261            return staticContext.getNamer();
262        }
263    
264        @NotNull
265        public Intrinsics intrinsics() {
266            return staticContext.getIntrinsics();
267        }
268    
269        @NotNull
270        public ReflectionTypes getReflectionTypes() {
271            return staticContext.getReflectionTypes();
272        }
273    
274        @NotNull
275        public JsProgram program() {
276            return staticContext.getProgram();
277        }
278    
279        @NotNull
280        public JsConfig getConfig() {
281            return staticContext.getConfig();
282        }
283    
284        @NotNull
285        public JsScope scope() {
286            return dynamicContext.getScope();
287        }
288    
289        @NotNull
290        public AliasingContext aliasingContext() {
291            return aliasingContext;
292        }
293    
294        @NotNull
295        public JsFunction getFunctionObject(@NotNull CallableDescriptor descriptor) {
296            return staticContext.getFunctionWithScope(descriptor);
297        }
298    
299        public void addStatementToCurrentBlock(@NotNull JsStatement statement) {
300            dynamicContext.jsBlock().getStatements().add(statement);
301        }
302    
303        public void addStatementsToCurrentBlockFrom(@NotNull TranslationContext context) {
304            addStatementsToCurrentBlockFrom(context.dynamicContext().jsBlock());
305        }
306    
307        public void addStatementsToCurrentBlockFrom(@NotNull JsBlock block) {
308            dynamicContext.jsBlock().getStatements().addAll(block.getStatements());
309        }
310    
311        public boolean currentBlockIsEmpty() {
312            return dynamicContext.jsBlock().isEmpty();
313        }
314    
315        public void moveVarsFrom(@NotNull TranslationContext context) {
316            dynamicContext.moveVarsFrom(context.dynamicContext());
317        }
318    
319        @NotNull
320        public JsBlock getCurrentBlock() {
321            return dynamicContext.jsBlock();
322        }
323    
324        @NotNull
325        public JsExpression getEmptyExpression() {
326            return program().getEmptyExpression();
327        }
328    
329        @Nullable
330        public JsExpression getAliasForDescriptor(@NotNull DeclarationDescriptor descriptor) {
331            JsNameRef nameRef = captureIfNeedAndGetCapturedName(descriptor);
332            if (nameRef != null) {
333                return nameRef;
334            }
335    
336            return aliasingContext.getAliasForDescriptor(descriptor);
337        }
338    
339        @NotNull
340        public JsExpression getDispatchReceiver(@NotNull ReceiverParameterDescriptor descriptor) {
341            JsExpression alias = getAliasForDescriptor(descriptor);
342            if (alias != null) {
343                return alias;
344            }
345            if (DescriptorUtils.isObject(descriptor.getContainingDeclaration())) {
346                if (isConstructorOrDirectScope(descriptor.getContainingDeclaration())) {
347                    return JsLiteral.THIS;
348                }
349                else {
350                    return getQualifiedReference(descriptor.getContainingDeclaration());
351                }
352            }
353    
354            if (descriptor.getValue() instanceof ExtensionReceiver) return JsLiteral.THIS;
355    
356            ClassifierDescriptor classifier = descriptor.getValue().getType().getConstructor().getDeclarationDescriptor();
357    
358            // TODO: can't tell why this assertion is valid, revisit this code later
359            assert classifier instanceof ClassDescriptor;
360    
361            ClassDescriptor cls = (ClassDescriptor) classifier;
362    
363            assert classDescriptor != null : "Can't get ReceiverParameterDescriptor in top level";
364            JsExpression receiver = getAliasForDescriptor(classDescriptor.getThisAsReceiverParameter());
365            if (receiver == null) {
366                receiver = JsLiteral.THIS;
367            }
368    
369            return getDispatchReceiverPath(cls, receiver);
370        }
371    
372        private boolean isConstructorOrDirectScope(DeclarationDescriptor descriptor) {
373            if (declarationDescriptor instanceof ClassDescriptor && !DescriptorUtils.isCompanionObject(declarationDescriptor)) {
374                return descriptor == declarationDescriptor;
375            }
376            else {
377                return declarationDescriptor != null && descriptor == DescriptorUtils.getContainingClass(declarationDescriptor);
378            }
379        }
380    
381        @NotNull
382        private JsExpression getDispatchReceiverPath(@Nullable ClassDescriptor cls, JsExpression thisExpression) {
383            if (cls != null) {
384                JsExpression alias = getAliasForDescriptor(cls);
385                if (alias != null) {
386                    return alias;
387                }
388            }
389    
390            if (classDescriptor == cls || parent == null) {
391                return thisExpression;
392            }
393    
394            ClassDescriptor parentDescriptor = parent.classDescriptor;
395            if (classDescriptor != parentDescriptor) {
396                return new JsNameRef(Namer.OUTER_FIELD_NAME, parent.getDispatchReceiverPath(cls, thisExpression));
397            }
398            else {
399                return parent.getDispatchReceiverPath(cls, thisExpression);
400            }
401        }
402    
403        @NotNull
404        public DefinitionPlace getDefinitionPlace() {
405            if (definitionPlace != null) return definitionPlace;
406            if (parent != null) return parent.getDefinitionPlace();
407    
408            throw new AssertionError("Can not find definition place from rootContext(definitionPlace and parent is null)");
409        }
410    
411        @NotNull
412        public JsNameRef define(DeclarationDescriptor descriptor, JsExpression expression) {
413            String suggestedName = TranslationUtils.getSuggestedNameForInnerDeclaration(staticContext, descriptor);
414            return getDefinitionPlace().define(suggestedName, expression);
415        }
416    
417        @Nullable
418        private JsNameRef captureIfNeedAndGetCapturedName(DeclarationDescriptor descriptor) {
419            if (usageTracker != null) {
420                usageTracker.used(descriptor);
421    
422                JsName name = getNameForCapturedDescriptor(usageTracker, descriptor);
423                if (name != null) {
424                    JsNameRef result = name.makeRef();
425                    if (shouldCaptureViaThis()) {
426                        result.setQualifier(JsLiteral.THIS);
427                    }
428                    return result;
429                }
430            }
431    
432            return null;
433        }
434    
435        private boolean shouldCaptureViaThis() {
436            if (declarationDescriptor == null) return false;
437    
438            if (DescriptorUtils.isDescriptorWithLocalVisibility(declarationDescriptor)) return false;
439            if (declarationDescriptor instanceof ConstructorDescriptor &&
440                DescriptorUtils.isDescriptorWithLocalVisibility(declarationDescriptor.getContainingDeclaration())) return false;
441    
442            return true;
443        }
444    
445        @Nullable
446        public DeclarationDescriptor getDeclarationDescriptor() {
447            return declarationDescriptor;
448        }
449    
450        public void putClassOrConstructorClosure(@NotNull MemberDescriptor descriptor, @NotNull List<DeclarationDescriptor> closure) {
451            staticContext.putClassOrConstructorClosure(descriptor, closure);
452        }
453    
454        @Nullable
455        public List<DeclarationDescriptor> getClassOrConstructorClosure(@NotNull MemberDescriptor classOrConstructor) {
456            List<DeclarationDescriptor> result = staticContext.getClassOrConstructorClosure(classOrConstructor);
457            if (result == null &&
458                classOrConstructor instanceof ConstructorDescriptor &&
459                ((ConstructorDescriptor) classOrConstructor).isPrimary()
460            ) {
461                result = staticContext.getClassOrConstructorClosure((ClassDescriptor) classOrConstructor.getContainingDeclaration());
462            }
463            return result;
464        }
465    
466        /**
467         * Gets an expression to pass to a constructor of a closure function. I.e. consider the case:
468         *
469         * ```
470         * fun a(x) {
471         *     fun b(y) = x + y
472         *     return b
473         * }
474         * ```
475         *
476         * Here, `x` is a free variable of `b`. Transform `a` into the following form:
477         *
478         * ```
479         * fun a(x) {
480         *     fun b0(x0) = { y -> x0 * y }
481         *     return b0(x)
482         * }
483         * ```
484         *
485         * This function generates arguments passed to newly generated `b0` closure, as well as for the similar case of local class and
486         * object expression.
487         *
488         * @param descriptor represents a free variable or, more generally, free declaration.
489         * @return expression to pass to a closure constructor.
490         */
491        @NotNull
492        public JsExpression getArgumentForClosureConstructor(@NotNull DeclarationDescriptor descriptor) {
493            JsExpression alias = getAliasForDescriptor(descriptor);
494            if (alias != null) return alias;
495            if (descriptor instanceof ReceiverParameterDescriptor) {
496                return getDispatchReceiver((ReceiverParameterDescriptor) descriptor);
497            }
498            return getNameForDescriptor(descriptor).makeRef();
499        }
500    
501        @Nullable
502        public JsName getOuterClassReference(ClassDescriptor descriptor) {
503            DeclarationDescriptor container = descriptor.getContainingDeclaration();
504            if (!(container instanceof ClassDescriptor) || !descriptor.isInner()) {
505                return null;
506            }
507    
508            return staticContext.getScopeForDescriptor(descriptor).declareName(Namer.OUTER_FIELD_NAME);
509        }
510    
511        public void startDeclaration() {
512            ClassDescriptor classDescriptor = this.classDescriptor;
513            if (classDescriptor != null && !(classDescriptor.getContainingDeclaration() instanceof ClassOrPackageFragmentDescriptor)) {
514                staticContext.getDeferredCallSites().put(classDescriptor, new ArrayList<DeferredCallSite>());
515            }
516        }
517    
518        @NotNull
519        public List<DeferredCallSite> endDeclaration() {
520            List<DeferredCallSite> result = null;
521            if (classDescriptor != null) {
522                result = staticContext.getDeferredCallSites().remove(classDescriptor);
523            }
524            if (result == null) {
525                result = Collections.emptyList();
526            }
527            return result;
528        }
529    
530        public boolean shouldBeDeferred(@NotNull ConstructorDescriptor constructor) {
531            ClassDescriptor classDescriptor = constructor.getContainingDeclaration();
532            return staticContext.getDeferredCallSites().containsKey(classDescriptor);
533        }
534    
535        public void deferConstructorCall(@NotNull ConstructorDescriptor constructor, @NotNull List<JsExpression> invocationArgs) {
536            ClassDescriptor classDescriptor = constructor.getContainingDeclaration();
537            List<DeferredCallSite> callSites = staticContext.getDeferredCallSites().get(classDescriptor);
538            if (callSites == null) throw new IllegalStateException("This method should be call only when `shouldBeDeferred` method " +
539                                                                   "reports true for given constructor: " + constructor);
540            callSites.add(new DeferredCallSite(constructor, invocationArgs, this));
541        }
542    }