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