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