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        @NotNull
233        public JsName getNameForBackingField(@NotNull PropertyDescriptor property) {
234            return staticContext.getNameForBackingField(property);
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 addStatementsToCurrentBlock(@NotNull Collection<JsStatement> statements) {
315            dynamicContext.jsBlock().getStatements().addAll(statements);
316        }
317    
318        public void addStatementsToCurrentBlockFrom(@NotNull TranslationContext context) {
319            addStatementsToCurrentBlockFrom(context.dynamicContext().jsBlock());
320        }
321    
322        public void addStatementsToCurrentBlockFrom(@NotNull JsBlock block) {
323            dynamicContext.jsBlock().getStatements().addAll(block.getStatements());
324        }
325    
326        public boolean currentBlockIsEmpty() {
327            return dynamicContext.jsBlock().isEmpty();
328        }
329    
330        public void moveVarsFrom(@NotNull TranslationContext context) {
331            dynamicContext.moveVarsFrom(context.dynamicContext());
332        }
333    
334        @NotNull
335        public JsBlock getCurrentBlock() {
336            return dynamicContext.jsBlock();
337        }
338    
339        @Nullable
340        public JsExpression getAliasForDescriptor(@NotNull DeclarationDescriptor descriptor) {
341            JsNameRef nameRef = captureIfNeedAndGetCapturedName(descriptor);
342            if (nameRef != null) {
343                return nameRef;
344            }
345    
346            return aliasingContext.getAliasForDescriptor(descriptor);
347        }
348    
349        @NotNull
350        public JsExpression getDispatchReceiver(@NotNull ReceiverParameterDescriptor descriptor) {
351            JsExpression alias = getAliasForDescriptor(descriptor);
352            if (alias != null) {
353                return alias;
354            }
355            if (DescriptorUtils.isObject(descriptor.getContainingDeclaration())) {
356                if (isConstructorOrDirectScope(descriptor.getContainingDeclaration())) {
357                    return JsLiteral.THIS;
358                }
359                else {
360                    return getQualifiedReference(descriptor.getContainingDeclaration());
361                }
362            }
363    
364            if (descriptor.getValue() instanceof ExtensionReceiver) return JsLiteral.THIS;
365    
366            ClassifierDescriptor classifier = descriptor.getValue().getType().getConstructor().getDeclarationDescriptor();
367    
368            // TODO: can't tell why this assertion is valid, revisit this code later
369            assert classifier instanceof ClassDescriptor;
370    
371            ClassDescriptor cls = (ClassDescriptor) classifier;
372    
373            assert classDescriptor != null : "Can't get ReceiverParameterDescriptor in top level";
374            JsExpression receiver = getAliasForDescriptor(classDescriptor.getThisAsReceiverParameter());
375            if (receiver == null) {
376                receiver = JsLiteral.THIS;
377            }
378    
379            return getDispatchReceiverPath(cls, receiver);
380        }
381    
382        private boolean isConstructorOrDirectScope(DeclarationDescriptor descriptor) {
383            if (declarationDescriptor instanceof ClassDescriptor && !DescriptorUtils.isCompanionObject(declarationDescriptor)) {
384                return descriptor == declarationDescriptor;
385            }
386            else {
387                return declarationDescriptor != null && descriptor == DescriptorUtils.getContainingClass(declarationDescriptor);
388            }
389        }
390    
391        @NotNull
392        private JsExpression getDispatchReceiverPath(@Nullable ClassDescriptor cls, JsExpression thisExpression) {
393            if (cls != null) {
394                JsExpression alias = getAliasForDescriptor(cls);
395                if (alias != null) {
396                    return alias;
397                }
398            }
399    
400            if (classDescriptor == cls || parent == null) {
401                return thisExpression;
402            }
403    
404            ClassDescriptor parentDescriptor = parent.classDescriptor;
405            if (classDescriptor != parentDescriptor) {
406                return new JsNameRef(Namer.OUTER_FIELD_NAME, parent.getDispatchReceiverPath(cls, thisExpression));
407            }
408            else {
409                return parent.getDispatchReceiverPath(cls, thisExpression);
410            }
411        }
412    
413        @NotNull
414        public DefinitionPlace getDefinitionPlace() {
415            if (definitionPlace != null) return definitionPlace;
416            if (parent != null) return parent.getDefinitionPlace();
417    
418            throw new AssertionError("Can not find definition place from rootContext(definitionPlace and parent is null)");
419        }
420    
421        @NotNull
422        public JsNameRef define(DeclarationDescriptor descriptor, JsExpression expression) {
423            String suggestedName = TranslationUtils.getSuggestedNameForInnerDeclaration(staticContext, descriptor);
424            return getDefinitionPlace().define(suggestedName, expression);
425        }
426    
427        @Nullable
428        private JsNameRef captureIfNeedAndGetCapturedName(DeclarationDescriptor descriptor) {
429            if (usageTracker != null) {
430                usageTracker.used(descriptor);
431    
432                JsName name = getNameForCapturedDescriptor(usageTracker, descriptor);
433                if (name != null) {
434                    JsNameRef result = name.makeRef();
435                    if (shouldCaptureViaThis()) {
436                        result.setQualifier(JsLiteral.THIS);
437                    }
438                    return result;
439                }
440            }
441    
442            return null;
443        }
444    
445        private boolean shouldCaptureViaThis() {
446            if (declarationDescriptor == null) return false;
447    
448            if (DescriptorUtils.isDescriptorWithLocalVisibility(declarationDescriptor)) return false;
449            if (declarationDescriptor instanceof ConstructorDescriptor &&
450                DescriptorUtils.isDescriptorWithLocalVisibility(declarationDescriptor.getContainingDeclaration())) return false;
451    
452            return true;
453        }
454    
455        @Nullable
456        public DeclarationDescriptor getDeclarationDescriptor() {
457            return declarationDescriptor;
458        }
459    
460        public void putClassOrConstructorClosure(@NotNull MemberDescriptor descriptor, @NotNull List<DeclarationDescriptor> closure) {
461            staticContext.putClassOrConstructorClosure(descriptor, closure);
462        }
463    
464        @Nullable
465        public List<DeclarationDescriptor> getClassOrConstructorClosure(@NotNull MemberDescriptor classOrConstructor) {
466            List<DeclarationDescriptor> result = staticContext.getClassOrConstructorClosure(classOrConstructor);
467            if (result == null &&
468                classOrConstructor instanceof ConstructorDescriptor &&
469                ((ConstructorDescriptor) classOrConstructor).isPrimary()
470            ) {
471                result = staticContext.getClassOrConstructorClosure((ClassDescriptor) classOrConstructor.getContainingDeclaration());
472            }
473            return result;
474        }
475    
476        /**
477         * Gets an expression to pass to a constructor of a closure function. I.e. consider the case:
478         *
479         * ```
480         * fun a(x) {
481         *     fun b(y) = x + y
482         *     return b
483         * }
484         * ```
485         *
486         * Here, `x` is a free variable of `b`. Transform `a` into the following form:
487         *
488         * ```
489         * fun a(x) {
490         *     fun b0(x0) = { y -> x0 * y }
491         *     return b0(x)
492         * }
493         * ```
494         *
495         * This function generates arguments passed to newly generated `b0` closure, as well as for the similar case of local class and
496         * object expression.
497         *
498         * @param descriptor represents a free variable or, more generally, free declaration.
499         * @return expression to pass to a closure constructor.
500         */
501        @NotNull
502        public JsExpression getArgumentForClosureConstructor(@NotNull DeclarationDescriptor descriptor) {
503            JsExpression alias = getAliasForDescriptor(descriptor);
504            if (alias != null) return alias;
505            if (descriptor instanceof ReceiverParameterDescriptor) {
506                return getDispatchReceiver((ReceiverParameterDescriptor) descriptor);
507            }
508            return getNameForDescriptor(descriptor).makeRef();
509        }
510    
511        @Nullable
512        public JsName getOuterClassReference(ClassDescriptor descriptor) {
513            DeclarationDescriptor container = descriptor.getContainingDeclaration();
514            if (!(container instanceof ClassDescriptor) || !descriptor.isInner()) {
515                return null;
516            }
517    
518            return staticContext.getScopeForDescriptor(descriptor).declareName(Namer.OUTER_FIELD_NAME);
519        }
520    
521        public void startDeclaration() {
522            ClassDescriptor classDescriptor = this.classDescriptor;
523            if (classDescriptor != null && !(classDescriptor.getContainingDeclaration() instanceof ClassOrPackageFragmentDescriptor)) {
524                staticContext.getDeferredCallSites().put(classDescriptor, new ArrayList<DeferredCallSite>());
525            }
526        }
527    
528        @NotNull
529        public List<DeferredCallSite> endDeclaration() {
530            List<DeferredCallSite> result = null;
531            if (classDescriptor != null) {
532                result = staticContext.getDeferredCallSites().remove(classDescriptor);
533            }
534            if (result == null) {
535                result = Collections.emptyList();
536            }
537            return result;
538        }
539    
540        public boolean shouldBeDeferred(@NotNull ConstructorDescriptor constructor) {
541            ClassDescriptor classDescriptor = constructor.getContainingDeclaration();
542            return staticContext.getDeferredCallSites().containsKey(classDescriptor);
543        }
544    
545        public void deferConstructorCall(@NotNull ConstructorDescriptor constructor, @NotNull List<JsExpression> invocationArgs) {
546            ClassDescriptor classDescriptor = constructor.getContainingDeclaration();
547            List<DeferredCallSite> callSites = staticContext.getDeferredCallSites().get(classDescriptor);
548            if (callSites == null) throw new IllegalStateException("This method should be call only when `shouldBeDeferred` method " +
549                                                                   "reports true for given constructor: " + constructor);
550            callSites.add(new DeferredCallSite(constructor, invocationArgs, this));
551        }
552    
553        @Nullable
554        public JsExpression getModuleExpressionFor(@NotNull DeclarationDescriptor descriptor) {
555            return staticContext.getModuleExpressionFor(descriptor);
556        }
557    }