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.reference;
018    
019    import com.google.dart.compiler.backend.js.ast.*;
020    import org.jetbrains.annotations.NotNull;
021    import org.jetbrains.annotations.Nullable;
022    import org.jetbrains.jet.lang.descriptors.*;
023    import org.jetbrains.jet.lang.psi.JetSuperExpression;
024    import org.jetbrains.jet.lang.resolve.calls.model.ResolvedCall;
025    import org.jetbrains.jet.lang.resolve.calls.model.VariableAsFunctionResolvedCall;
026    import org.jetbrains.jet.lang.resolve.calls.util.ExpressionAsFunctionDescriptor;
027    import org.jetbrains.jet.lang.resolve.scopes.receivers.ExpressionReceiver;
028    import org.jetbrains.jet.lang.resolve.scopes.receivers.ReceiverValue;
029    import org.jetbrains.k2js.translate.context.Namer;
030    import org.jetbrains.k2js.translate.context.TranslationContext;
031    import org.jetbrains.k2js.translate.general.AbstractTranslator;
032    import org.jetbrains.k2js.translate.intrinsic.functions.basic.FunctionIntrinsic;
033    import org.jetbrains.k2js.translate.utils.AnnotationsUtils;
034    import org.jetbrains.k2js.translate.utils.ErrorReportingUtils;
035    import org.jetbrains.k2js.translate.utils.TranslationUtils;
036    
037    import java.util.ArrayList;
038    import java.util.List;
039    
040    import static org.jetbrains.k2js.translate.reference.CallParametersResolver.resolveCallParameters;
041    import static org.jetbrains.k2js.translate.utils.JsAstUtils.assignment;
042    import static org.jetbrains.k2js.translate.utils.JsAstUtils.setQualifier;
043    
044    //TODO: write tests on calling backing fields as functions
045    public final class CallTranslator extends AbstractTranslator {
046        @NotNull
047        private final List<JsExpression> arguments;
048        @NotNull
049        private final ResolvedCall<?> resolvedCall;
050        @NotNull
051        private final CallableDescriptor descriptor;
052        @NotNull
053        private final CallType callType;
054        @NotNull
055        private final CallParameters callParameters;
056    
057        /*package*/ CallTranslator(@Nullable JsExpression receiver, @Nullable JsExpression callee,
058                @NotNull List<JsExpression> arguments,
059                @NotNull ResolvedCall<? extends CallableDescriptor> resolvedCall,
060                @NotNull CallableDescriptor descriptorToCall,
061                @NotNull CallType callType,
062                @NotNull TranslationContext context) {
063            super(context);
064            this.arguments = arguments;
065            this.resolvedCall = resolvedCall;
066            this.callType = callType;
067            this.descriptor = descriptorToCall;
068            this.callParameters = resolveCallParameters(receiver, callee, descriptor, resolvedCall, context);
069        }
070    
071        @NotNull
072        public ResolvedCall<? extends CallableDescriptor> getResolvedCall() {
073            return resolvedCall;
074        }
075    
076        @NotNull
077        public CallParameters getCallParameters() {
078            return callParameters;
079        }
080    
081        @NotNull
082        /*package*/ JsExpression translate() {
083            JsExpression result = intrinsicInvocation();
084            if (result != null) {
085                return result;
086            }
087            if ((descriptor instanceof ConstructorDescriptor)) {
088                return createConstructorCallExpression(translateAsFunctionWithNoThisObject(descriptor));
089            }
090            if (resolvedCall.getReceiverArgument().exists()) {
091                if (AnnotationsUtils.isNativeObject(descriptor)) {
092                    return nativeExtensionCall();
093                }
094                return extensionFunctionCall(!(descriptor instanceof ExpressionAsFunctionDescriptor));
095            }
096            if (isExpressionAsFunction()) {
097                return expressionAsFunctionCall();
098            }
099            if (isSuperInvocation()) {
100                return superMethodCall(getThisObjectOrQualifier());
101            }
102    
103            return methodCall(getThisObjectOrQualifier());
104        }
105    
106        private boolean isExpressionAsFunction() {
107            return descriptor instanceof ExpressionAsFunctionDescriptor ||
108                   resolvedCall instanceof VariableAsFunctionResolvedCall;
109        }
110    
111        @NotNull
112        private JsExpression expressionAsFunctionCall() {
113            return methodCall(null);
114        }
115    
116        @Nullable
117        private JsExpression intrinsicInvocation() {
118            if (descriptor instanceof FunctionDescriptor) {
119                try {
120                    FunctionIntrinsic intrinsic = context().intrinsics().getFunctionIntrinsics().getIntrinsic((FunctionDescriptor) descriptor);
121                    if (intrinsic.exists()) {
122                        return intrinsic.apply(this, arguments, context());
123                    }
124                }
125                catch (RuntimeException e) {
126                    throw ErrorReportingUtils.reportErrorWithLocation(e, descriptor, bindingContext());
127                }
128            }
129            return null;
130        }
131    
132        @NotNull
133        public HasArguments createConstructorCallExpression(@NotNull JsExpression constructorReference) {
134            return new JsNew(constructorReference, arguments);
135        }
136    
137        @NotNull
138        private JsExpression translateAsFunctionWithNoThisObject(@NotNull DeclarationDescriptor descriptor) {
139            return ReferenceTranslator.translateAsFQReference(descriptor, context());
140        }
141    
142        @NotNull
143        private JsExpression nativeExtensionCall() {
144            return methodCall(callParameters.getReceiver());
145        }
146    
147        @NotNull
148        public JsExpression explicitInvokeCall() {
149            return callType.constructCall(callParameters.getThisObject(), new CallType.CallConstructor() {
150                @NotNull
151                @Override
152                public JsExpression construct(@Nullable JsExpression receiver) {
153                    return new JsInvocation(receiver, arguments);
154                }
155            }, context());
156        }
157    
158        @NotNull
159        public JsExpression extensionFunctionCall(final boolean useThis) {
160            return callType.constructCall(callParameters.getReceiver(), new CallType.CallConstructor() {
161                @NotNull
162                @Override
163                public JsExpression construct(@Nullable JsExpression receiver) {
164                    assert receiver != null : "Could not be null for extensions";
165                    JsExpression functionReference = callParameters.getFunctionReference();
166                    if (useThis) {
167                        setQualifier(functionReference, getThisObjectOrQualifier());
168                    }
169                    return new JsInvocation(callParameters.getFunctionReference(), generateCallArgumentList(receiver));
170                }
171            }, context());
172        }
173    
174        @NotNull
175        private List<JsExpression> generateCallArgumentList(@NotNull JsExpression receiver) {
176            return TranslationUtils.generateInvocationArguments(receiver, arguments);
177        }
178    
179        private boolean isSuperInvocation() {
180            ReceiverValue thisObject = resolvedCall.getThisObject();
181            return thisObject instanceof ExpressionReceiver && ((ExpressionReceiver) thisObject).getExpression() instanceof JetSuperExpression;
182        }
183    
184        @NotNull
185        private JsExpression superMethodCall(@Nullable JsExpression receiver) {
186            return callType.constructCall(receiver, new CallType.CallConstructor() {
187                @NotNull
188                @Override
189                public JsExpression construct(@Nullable JsExpression receiver) {
190                    assert receiver != null : "Receiver for superCall must be not null";
191                    if (isDirectPropertyAccess()) {
192                        return superPropertyAccess(receiver, getPropertyName());
193                    }
194    
195                    JsExpression qualifiedCallee = getQualifiedCallee(Namer.getRefToPrototype(receiver));
196                    qualifiedCallee = Namer.getFunctionCallRef(qualifiedCallee);
197                    List<JsExpression> arguments = new ArrayList<JsExpression>(CallTranslator.this.arguments.size() + 1);
198                    arguments.add(JsLiteral.THIS);
199                    arguments.addAll(CallTranslator.this.arguments);
200                    return new JsInvocation(qualifiedCallee, arguments);
201                }
202            }, context());
203        }
204    
205        @NotNull
206        private JsStringLiteral getPropertyName() {
207            assert descriptor instanceof PropertyAccessorDescriptor;
208            String propertyName = ((PropertyAccessorDescriptor) descriptor).getCorrespondingProperty().getName().asString();
209            return context().program().getStringLiteral(propertyName);
210        }
211    
212        @NotNull
213        private JsExpression superPropertyAccess(@NotNull JsExpression classRef, @NotNull JsStringLiteral propertyName) {
214            if (descriptor instanceof PropertyGetterDescriptor) {
215                assert arguments.isEmpty();
216                return new JsInvocation(context().namer().getCallGetProperty(), JsLiteral.THIS, classRef, propertyName);
217            } else {
218                assert descriptor instanceof PropertySetterDescriptor;
219                assert arguments.size() == 1;
220                return new JsInvocation(context().namer().getCallSetProperty(), JsLiteral.THIS, classRef, propertyName, arguments.get(0));
221            }
222        }
223    
224        @NotNull
225        private JsExpression methodCall(@Nullable JsExpression receiver) {
226            return callType.constructCall(receiver, new CallType.CallConstructor() {
227                @NotNull
228                @Override
229                public JsExpression construct(@Nullable JsExpression receiver) {
230                    JsExpression qualifiedCallee = getQualifiedCallee(receiver);
231                    if (isDirectPropertyAccess()) {
232                        return directPropertyAccess(qualifiedCallee);
233                    }
234    
235                    return new JsInvocation(qualifiedCallee, arguments);
236                }
237            }, context());
238        }
239    
240        @NotNull
241        private JsExpression directPropertyAccess(@NotNull JsExpression callee) {
242            if (descriptor instanceof PropertyGetterDescriptor) {
243                assert arguments.isEmpty();
244                return callee;
245            }
246            else {
247                assert descriptor instanceof PropertySetterDescriptor;
248                assert arguments.size() == 1;
249                return assignment(callee, arguments.get(0));
250            }
251        }
252    
253        private boolean isDirectPropertyAccess() {
254            return descriptor instanceof PropertyAccessorDescriptor;
255        }
256    
257        @NotNull
258        private JsExpression getQualifiedCallee(@Nullable JsExpression receiver) {
259            JsExpression callee = callParameters.getFunctionReference();
260            if (receiver != null) {
261                setQualifier(callee, receiver);
262            }
263            return callee;
264        }
265    
266        @Nullable
267        private JsExpression getThisObjectOrQualifier() {
268            JsExpression thisObject = callParameters.getThisObject();
269            if (thisObject != null) {
270                return thisObject;
271            }
272            return context().getQualifierForDescriptor(descriptor);
273        }
274    }