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.k2js.translate.expression;
018    
019    import com.google.dart.compiler.backend.js.ast.*;
020    import com.intellij.util.SmartList;
021    import org.jetbrains.annotations.NotNull;
022    import org.jetbrains.annotations.Nullable;
023    import org.jetbrains.jet.lang.descriptors.*;
024    import org.jetbrains.jet.lang.descriptors.impl.AnonymousFunctionDescriptor;
025    import org.jetbrains.jet.lang.psi.JetClassBody;
026    import org.jetbrains.jet.lang.psi.JetClassOrObject;
027    import org.jetbrains.jet.lang.psi.JetDeclarationWithBody;
028    import org.jetbrains.jet.lang.resolve.DescriptorUtils;
029    import org.jetbrains.k2js.translate.context.AliasingContext;
030    import org.jetbrains.k2js.translate.context.Namer;
031    import org.jetbrains.k2js.translate.context.TranslationContext;
032    import org.jetbrains.k2js.translate.context.UsageTracker;
033    import org.jetbrains.k2js.translate.declaration.ClassTranslator;
034    import org.jetbrains.k2js.translate.general.AbstractTranslator;
035    import org.jetbrains.k2js.translate.utils.JsAstUtils;
036    
037    import java.util.List;
038    
039    import static org.jetbrains.k2js.translate.utils.BindingUtils.getFunctionDescriptor;
040    import static org.jetbrains.k2js.translate.utils.FunctionBodyTranslator.translateFunctionBody;
041    import static org.jetbrains.k2js.translate.utils.JsDescriptorUtils.getExpectedReceiverDescriptor;
042    
043    public class LiteralFunctionTranslator extends AbstractTranslator {
044        private static final String CAPTURED_VALUE_FIELD = "v";
045    
046        private final JetDeclarationWithBody declaration;
047        private final FunctionDescriptor descriptor;
048        private final JsFunction jsFunction;
049        private final TranslationContext functionContext;
050        private final boolean inConstructorOrTopLevel;
051        private final ClassDescriptor outerClass;
052        private final JsName receiverName;
053        private JsNameRef tempRef = null;
054    
055        private LiteralFunctionTranslator(@NotNull JetDeclarationWithBody declaration, @NotNull TranslationContext context) {
056            super(context);
057    
058            this.declaration = declaration;
059            this.descriptor = getFunctionDescriptor(context().bindingContext(), declaration);
060    
061            DeclarationDescriptor receiverDescriptor = getExpectedReceiverDescriptor(descriptor);
062            jsFunction = new JsFunction(context().scope(), new JsBlock());
063    
064            AliasingContext aliasingContext;
065            if (receiverDescriptor == null) {
066                receiverName = null;
067                aliasingContext = null;
068            }
069            else {
070                receiverName = jsFunction.getScope().declareName(Namer.getReceiverParameterName());
071                aliasingContext = context().aliasingContext().inner(receiverDescriptor, receiverName.makeRef());
072            }
073    
074            if (descriptor.getContainingDeclaration() instanceof ConstructorDescriptor) {
075                // KT-2388
076                inConstructorOrTopLevel = true;
077                jsFunction.setName(jsFunction.getScope().declareName(Namer.CALLEE_NAME));
078                outerClass = (ClassDescriptor) descriptor.getContainingDeclaration().getContainingDeclaration();
079                assert outerClass != null;
080    
081                if (aliasingContext == null) {
082                    aliasingContext = context().aliasingContext();
083                }
084    
085                aliasingContext = aliasingContext.notShareableThisAliased(outerClass, new JsNameRef("o", jsFunction.getName().makeRef()));
086            }
087            else {
088                outerClass = null;
089                inConstructorOrTopLevel = DescriptorUtils.isTopLevelDeclaration(descriptor);
090            }
091    
092            UsageTracker funTracker = new UsageTracker(descriptor, context().usageTracker(), outerClass);
093            functionContext = context().newFunctionBody(jsFunction, aliasingContext, funTracker);
094        }
095    
096        private void translateBody() {
097            JsBlock functionBody = translateFunctionBody(descriptor, declaration, functionContext);
098            jsFunction.getBody().getStatements().addAll(functionBody.getStatements());
099        }
100    
101        @NotNull
102        private JsExpression finish() {
103            JsExpression result;
104    
105            if (inConstructorOrTopLevel) {
106                result = jsFunction;
107    
108                if (outerClass != null) {
109                    UsageTracker usageTracker = functionContext.usageTracker();
110                    assert usageTracker != null;
111                    if (usageTracker.isUsed()) {
112                        result = new JsInvocation(context().namer().kotlin("assignOwner"), jsFunction, JsLiteral.THIS);
113                    }
114                    else {
115                        jsFunction.setName(null);
116                    }
117                }
118            }
119            else {
120                JsNameRef funReference = context().define(getSuggestedName(functionContext, descriptor), jsFunction);
121    
122                InnerFunctionTranslator innerTranslator = new InnerFunctionTranslator(descriptor, functionContext, jsFunction, tempRef);
123                result = innerTranslator.translate(funReference, context());
124            }
125    
126            addRegularParameters(descriptor, jsFunction, functionContext, receiverName);
127    
128            return result;
129        }
130    
131        @NotNull
132        private JsExpression translate() {
133            translateBody();
134            return finish();
135        }
136    
137        @NotNull
138        public JsVars translateLocalNamedFunction() {
139            // Add ability to capture this named function.
140            // Will be available like `foo.v` (for function `foo`)
141            // Can not generate direct call because function may have some closures.
142            JsName funName = functionContext.getNameForDescriptor(descriptor);
143            JsNameRef alias = new JsNameRef(CAPTURED_VALUE_FIELD, funName.makeRef());
144            functionContext.aliasingContext().registerAlias(descriptor, alias);
145    
146            translateBody();
147    
148            UsageTracker funTracker = functionContext.usageTracker();
149            assert funTracker != null;
150            boolean funIsCaptured = funTracker.isCaptured(descriptor);
151    
152            // Create temporary variable name which will be contain reference to the function.
153            JsName temp;
154            if (funIsCaptured) {
155                assert !inConstructorOrTopLevel : "A recursive closure in constructor is unsupported.";
156                // Use `context()` because it should be created in the scope which contain call.
157                temp = context().scope().declareTemporary();
158                tempRef = temp.makeRef();
159            }
160            else {
161                temp = null;
162            }
163    
164            JsExpression result = finish();
165    
166            List<JsVars.JsVar> vars = new SmartList<JsVars.JsVar>();
167    
168            if (funIsCaptured) {
169                JsVars.JsVar tempVar = new JsVars.JsVar(temp, new JsObjectLiteral());
170                vars.add(tempVar);
171    
172                // Save `result` to the field of temporary variable if the function is captured.
173                result = JsAstUtils.assignment(new JsNameRef(CAPTURED_VALUE_FIELD, temp.makeRef()), result);
174    
175            }
176    
177            JsVars.JsVar fun = new JsVars.JsVar(funName, result);
178            vars.add(fun);
179    
180            return new JsVars(vars, /*mulitline =*/ false);
181        }
182    
183        private static void addRegularParameters(
184                @NotNull FunctionDescriptor descriptor,
185                @NotNull JsFunction fun,
186                @NotNull TranslationContext funContext,
187                @Nullable JsName receiverName
188        ) {
189            if (receiverName != null) {
190                fun.getParameters().add(new JsParameter(receiverName));
191            }
192            FunctionTranslator.addParameters(fun.getParameters(), descriptor, funContext);
193        }
194    
195        private static String getSuggestedName(TranslationContext context, DeclarationDescriptor descriptor) {
196            String suggestedName = "";
197            DeclarationDescriptor containingDeclaration = descriptor.getContainingDeclaration();
198            if (containingDeclaration != null &&
199                !(containingDeclaration instanceof ClassOrPackageFragmentDescriptor) &&
200                !(containingDeclaration instanceof AnonymousFunctionDescriptor)) {
201                suggestedName = context.getNameForDescriptor(containingDeclaration).getIdent();
202            }
203    
204            if (!suggestedName.isEmpty() && !suggestedName.endsWith("$")) {
205                suggestedName += "$";
206            }
207    
208            if (descriptor.getName().isSpecial()) {
209                suggestedName += "f";
210            }
211            else {
212                suggestedName += context.getNameForDescriptor(descriptor).getIdent();
213            }
214            return suggestedName;
215        }
216    
217        @NotNull
218        public static JsVars translateLocalNamedFunction(@NotNull JetDeclarationWithBody declaration, @NotNull TranslationContext outerContext) {
219            return new LiteralFunctionTranslator(declaration, outerContext).translateLocalNamedFunction();
220        }
221    
222        @NotNull
223        public static JsExpression translate(@NotNull JetDeclarationWithBody declaration, @NotNull TranslationContext outerContext) {
224            return new LiteralFunctionTranslator(declaration, outerContext).translate();
225        }
226    
227        // TODO: Probably should be moved to other place
228        @NotNull
229        public static JsExpression translate(
230                @NotNull ClassDescriptor outerClass,
231                @NotNull TranslationContext outerClassContext,
232                @NotNull JetClassOrObject declaration,
233                @NotNull ClassDescriptor descriptor,
234                @NotNull ClassTranslator classTranslator
235        ) {
236            JsFunction fun = new JsFunction(outerClassContext.scope(), new JsBlock());
237            JsNameRef outerClassRef = fun.getScope().declareName(Namer.OUTER_CLASS_NAME).makeRef();
238            UsageTracker usageTracker = new UsageTracker(descriptor, outerClassContext.usageTracker(), outerClass);
239            AliasingContext aliasingContext = outerClassContext.aliasingContext().inner(outerClass, outerClassRef);
240            TranslationContext funContext = outerClassContext.newFunctionBody(fun, aliasingContext, usageTracker);
241    
242            fun.getBody().getStatements().add(new JsReturn(classTranslator.translate(funContext)));
243    
244            JetClassBody body = declaration.getBody();
245            assert body != null;
246    
247            JsNameRef define = funContext.define(getSuggestedName(funContext, descriptor), fun);
248            return new InnerObjectTranslator(funContext, fun).translate(define, usageTracker.isUsed() ? outerClassRef : null);
249        }
250    }