001    /*
002     * Copyright 2010-2013 JetBrains s.r.o.
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     * http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    
017    package org.jetbrains.kotlin.js.translate.reference;
018    
019    import com.google.gwt.dev.js.ThrowExceptionOnErrorReporter;
020    import org.jetbrains.annotations.NotNull;
021    import org.jetbrains.annotations.Nullable;
022    import org.jetbrains.kotlin.config.CommonConfigurationKeys;
023    import org.jetbrains.kotlin.descriptors.*;
024    import org.jetbrains.kotlin.incremental.components.NoLookupLocation;
025    import org.jetbrains.kotlin.js.backend.ast.*;
026    import org.jetbrains.kotlin.js.inline.util.CollectUtilsKt;
027    import org.jetbrains.kotlin.js.inline.util.RewriteUtilsKt;
028    import org.jetbrains.kotlin.js.parser.ParserUtilsKt;
029    import org.jetbrains.kotlin.js.resolve.BindingContextSlicesJsKt;
030    import org.jetbrains.kotlin.js.resolve.diagnostics.JsCallChecker;
031    import org.jetbrains.kotlin.js.translate.callTranslator.CallTranslator;
032    import org.jetbrains.kotlin.js.translate.context.TranslationContext;
033    import org.jetbrains.kotlin.name.Name;
034    import org.jetbrains.kotlin.psi.KtCallExpression;
035    import org.jetbrains.kotlin.psi.KtExpression;
036    import org.jetbrains.kotlin.psi.ValueArgument;
037    import org.jetbrains.kotlin.resolve.FunctionImportedFromObject;
038    import org.jetbrains.kotlin.resolve.calls.callUtil.CallUtilKt;
039    import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall;
040    import org.jetbrains.kotlin.resolve.inline.InlineUtil;
041    import org.jetbrains.kotlin.resolve.scopes.LexicalScope;
042    
043    import java.util.*;
044    
045    import static org.jetbrains.kotlin.js.resolve.diagnostics.JsCallChecker.isJsCall;
046    import static org.jetbrains.kotlin.resolve.constants.evaluate.ConstantExpressionEvaluator.getConstant;
047    
048    public final class CallExpressionTranslator extends AbstractCallExpressionTranslator {
049    
050        @NotNull
051        public static JsNode translate(
052                @NotNull KtCallExpression expression,
053                @Nullable JsExpression receiver,
054                @NotNull TranslationContext context
055        ) {
056            ResolvedCall<? extends FunctionDescriptor> resolvedCall =
057                    CallUtilKt.getFunctionResolvedCallWithAssert(expression, context.bindingContext());
058    
059            if (isJsCall(resolvedCall)) {
060                return (new CallExpressionTranslator(expression, receiver, context)).translateJsCode();
061            }
062    
063            return (new CallExpressionTranslator(expression, receiver, context)).translate();
064        }
065    
066        public static boolean shouldBeInlined(@NotNull CallableDescriptor descriptor, @NotNull TranslationContext context) {
067            if (context.getConfig().getConfiguration().getBoolean(CommonConfigurationKeys.DISABLE_INLINE)) return false;
068    
069            return shouldBeInlined(descriptor);
070        }
071    
072        public static boolean shouldBeInlined(@NotNull CallableDescriptor descriptor) {
073            if (descriptor instanceof SimpleFunctionDescriptor ||
074                descriptor instanceof PropertyAccessorDescriptor ||
075                descriptor instanceof FunctionImportedFromObject
076            ) {
077                return InlineUtil.isInline(descriptor);
078            }
079    
080            if (descriptor instanceof ValueParameterDescriptor) {
081                return InlineUtil.isInline(descriptor.getContainingDeclaration()) &&
082                       InlineUtil.isInlineLambdaParameter((ParameterDescriptor) descriptor) &&
083                       !((ValueParameterDescriptor) descriptor).isCrossinline();
084            }
085    
086            return false;
087        }
088    
089        private CallExpressionTranslator(
090                @NotNull KtCallExpression expression,
091                @Nullable JsExpression receiver,
092                @NotNull TranslationContext context
093        ) {
094            super(expression, receiver, context);
095        }
096    
097        @NotNull
098        private JsExpression translate() {
099            return CallTranslator.translate(context(), resolvedCall, receiver);
100        }
101    
102        @NotNull
103        private JsNode translateJsCode() {
104            List<? extends ValueArgument> arguments = expression.getValueArguments();
105            KtExpression argumentExpression = arguments.get(0).getArgumentExpression();
106            assert argumentExpression != null;
107    
108            List<JsStatement> statements = parseJsCode(argumentExpression);
109            int size = statements.size();
110    
111            JsNode node;
112            if (size == 0) {
113                node = JsLiteral.NULL;
114            }
115            else if (size > 1) {
116                node = new JsBlock(statements);
117            }
118            else {
119                JsStatement resultStatement = statements.get(0);
120                if (resultStatement instanceof JsExpressionStatement) {
121                    node = ((JsExpressionStatement) resultStatement).getExpression();
122                }
123                else {
124                    node = resultStatement;
125                }
126            }
127    
128            LexicalScope lexicalScope = context().bindingContext().get(BindingContextSlicesJsKt.LEXICAL_SCOPE_FOR_JS, resolvedCall);
129            Map<JsName, JsExpression> replacements = new HashMap<JsName, JsExpression>();
130            if (lexicalScope != null) {
131                Set<JsName> references = CollectUtilsKt.collectUsedNames(node);
132                references.removeAll(CollectUtilsKt.collectDefinedNames(node));
133    
134                for (JsName name : references) {
135                    VariableDescriptor variable = getVariableByName(lexicalScope, Name.identifier(name.getIdent()));
136                    if (variable != null) {
137                        replacements.put(name, ReferenceTranslator.translateAsValueReference(variable, context()));
138                    }
139                }
140    
141                if (!replacements.isEmpty()) {
142                    node = RewriteUtilsKt.replaceNames(node, replacements);
143                }
144            }
145    
146            return node;
147        }
148    
149        @Nullable
150        private static VariableDescriptor getVariableByName(@NotNull LexicalScope scope, @NotNull Name name) {
151            while (true) {
152                Collection<VariableDescriptor> variables = scope.getContributedVariables(name, NoLookupLocation.FROM_BACKEND);
153                if (!variables.isEmpty()) {
154                    return variables.size() == 1 ? variables.iterator().next() : null;
155                }
156    
157                if (!(scope.getParent() instanceof LexicalScope)) break;
158                LexicalScope parentScope = (LexicalScope) scope.getParent();
159                if (scope.getOwnerDescriptor() != parentScope.getOwnerDescriptor()) break;
160                scope = parentScope;
161            }
162            return null;
163        }
164    
165        @NotNull
166        private List<JsStatement> parseJsCode(@NotNull KtExpression jsCodeExpression) {
167            String jsCode = JsCallChecker.extractStringValue(getConstant(jsCodeExpression, context().bindingContext()));
168    
169            assert jsCode != null : "jsCode must be compile time string " + jsCodeExpression.getText();
170    
171            // Parser can change local or global scope.
172            // In case of js we want to keep new local names,
173            // but no new global ones.
174            JsScope currentScope = context().scope();
175            assert currentScope instanceof JsFunctionScope : "Usage of js outside of function is unexpected";
176            JsScope temporaryRootScope = new JsRootScope(new JsProgram());
177            JsScope scope = new DelegatingJsFunctionScopeWithTemporaryParent((JsFunctionScope) currentScope, temporaryRootScope);
178            return ParserUtilsKt.parse(jsCode, ThrowExceptionOnErrorReporter.INSTANCE, scope);
179        }
180    }