001    /*
002     * Copyright 2010-2015 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.utils;
018    
019    import org.jetbrains.annotations.NotNull;
020    import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
021    import org.jetbrains.kotlin.descriptors.CallableDescriptor;
022    import org.jetbrains.kotlin.descriptors.DeclarationDescriptor;
023    import org.jetbrains.kotlin.descriptors.FunctionDescriptor;
024    import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor;
025    import org.jetbrains.kotlin.js.backend.ast.*;
026    import org.jetbrains.kotlin.js.backend.ast.metadata.MetadataProperties;
027    import org.jetbrains.kotlin.js.naming.NameSuggestion;
028    import org.jetbrains.kotlin.js.translate.context.Namer;
029    import org.jetbrains.kotlin.js.translate.context.TranslationContext;
030    import org.jetbrains.kotlin.js.translate.expression.LocalFunctionCollector;
031    import org.jetbrains.kotlin.js.translate.general.AbstractTranslator;
032    import org.jetbrains.kotlin.js.translate.general.Translation;
033    import org.jetbrains.kotlin.js.translate.utils.mutator.Mutator;
034    import org.jetbrains.kotlin.psi.KtDeclarationWithBody;
035    import org.jetbrains.kotlin.psi.KtExpression;
036    import org.jetbrains.kotlin.types.KotlinType;
037    import org.jetbrains.kotlin.types.TypeUtils;
038    
039    import java.util.ArrayList;
040    import java.util.HashMap;
041    import java.util.List;
042    import java.util.Map;
043    
044    import static org.jetbrains.kotlin.js.translate.utils.BindingUtils.getDefaultArgument;
045    import static org.jetbrains.kotlin.js.translate.utils.JsAstUtils.*;
046    import static org.jetbrains.kotlin.js.translate.utils.mutator.LastExpressionMutator.mutateLastExpression;
047    
048    public final class FunctionBodyTranslator extends AbstractTranslator {
049    
050        @NotNull
051        public static JsBlock translateFunctionBody(
052                @NotNull FunctionDescriptor descriptor,
053                @NotNull KtDeclarationWithBody declarationWithBody,
054                @NotNull TranslationContext functionBodyContext
055        ) {
056            Map<DeclarationDescriptor, JsExpression> aliases = new HashMap<DeclarationDescriptor, JsExpression>();
057            LocalFunctionCollector functionCollector = new LocalFunctionCollector(functionBodyContext.bindingContext());
058            declarationWithBody.acceptChildren(functionCollector, null);
059    
060            for (FunctionDescriptor localFunction : functionCollector.getFunctions()) {
061                String localIdent = localFunction.getName().isSpecial() ? "lambda" : localFunction.getName().asString();
062                JsName localName = functionBodyContext.scope().getParent().declareTemporaryName(NameSuggestion.sanitizeName(localIdent));
063                MetadataProperties.setDescriptor(localName, localFunction);
064                JsExpression alias = JsAstUtils.pureFqn(localName, null);
065                aliases.put(localFunction, alias);
066            }
067    
068            if (!aliases.isEmpty()) {
069                functionBodyContext = functionBodyContext.innerContextWithDescriptorsAliased(aliases);
070            }
071    
072            return (new FunctionBodyTranslator(descriptor, declarationWithBody, functionBodyContext)).translate();
073        }
074    
075        @NotNull
076        public static List<JsStatement> setDefaultValueForArguments(@NotNull FunctionDescriptor descriptor,
077                @NotNull TranslationContext functionBodyContext) {
078            List<ValueParameterDescriptor> valueParameters = descriptor.getValueParameters();
079    
080            List<JsStatement> result = new ArrayList<JsStatement>(valueParameters.size());
081            for (ValueParameterDescriptor valueParameter : valueParameters) {
082                if (!valueParameter.declaresDefaultValue()) continue;
083    
084                JsNameRef jsNameRef = functionBodyContext.getNameForDescriptor(valueParameter).makeRef();
085                KtExpression defaultArgument = getDefaultArgument(valueParameter);
086                JsBlock defaultArgBlock = new JsBlock();
087                JsExpression defaultValue = Translation.translateAsExpression(defaultArgument, functionBodyContext, defaultArgBlock);
088                JsStatement assignStatement = assignment(jsNameRef, defaultValue).makeStmt();
089                JsStatement thenStatement = JsAstUtils.mergeStatementInBlockIfNeeded(assignStatement, defaultArgBlock);
090                JsBinaryOperation checkArgIsUndefined = equality(jsNameRef, Namer.getUndefinedExpression());
091                JsIf jsIf = JsAstUtils.newJsIf(checkArgIsUndefined, thenStatement);
092                result.add(jsIf);
093            }
094    
095            return result;
096        }
097    
098        @NotNull
099        private final FunctionDescriptor descriptor;
100        @NotNull
101        private final KtDeclarationWithBody declaration;
102    
103        private FunctionBodyTranslator(@NotNull FunctionDescriptor descriptor,
104                                       @NotNull KtDeclarationWithBody declaration,
105                                       @NotNull TranslationContext context) {
106            super(context);
107            this.descriptor = descriptor;
108            this.declaration = declaration;
109        }
110    
111        @NotNull
112        private JsBlock translate() {
113            KtExpression jetBodyExpression = declaration.getBodyExpression();
114            assert jetBodyExpression != null : "Cannot translate a body of an abstract function.";
115            JsBlock jsBlock = new JsBlock();
116    
117    
118            JsNode jsBody = Translation.translateExpression(jetBodyExpression, context(), jsBlock);
119            jsBlock.getStatements().addAll(mayBeWrapWithReturn(jsBody).getStatements());
120            return jsBlock;
121        }
122    
123        @NotNull
124        private JsBlock mayBeWrapWithReturn(@NotNull JsNode body) {
125            if (!mustAddReturnToGeneratedFunctionBody()) {
126                return convertToBlock(body);
127            }
128            return convertToBlock(lastExpressionReturned(body));
129        }
130    
131        private boolean mustAddReturnToGeneratedFunctionBody() {
132            KotlinType functionReturnType = descriptor.getReturnType();
133            assert functionReturnType != null : "Function return typed type must be resolved.";
134            return (!declaration.hasBlockBody()) && !(KotlinBuiltIns.isUnit(functionReturnType) && !descriptor.isSuspend());
135        }
136    
137        @NotNull
138        private JsNode lastExpressionReturned(@NotNull JsNode body) {
139            return mutateLastExpression(body, new Mutator() {
140                @Override
141                @NotNull
142                public JsNode mutate(@NotNull JsNode node) {
143                    if (!(node instanceof JsExpression)) {
144                        return node;
145                    }
146    
147                    KotlinType bodyType = context().bindingContext().getType(declaration.getBodyExpression());
148                    if (bodyType == null && KotlinBuiltIns.isCharOrNullableChar(descriptor.getReturnType()) ||
149                        bodyType != null && KotlinBuiltIns.isCharOrNullableChar(bodyType) && TranslationUtils.shouldBoxReturnValue(descriptor)) {
150                        node = JsAstUtils.charToBoxedChar((JsExpression) node);
151                    }
152    
153                    JsReturn jsReturn = new JsReturn((JsExpression)node);
154                    MetadataProperties.setReturnTarget(jsReturn, descriptor);
155                    return jsReturn;
156                }
157            });
158        }
159    }