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
017package org.jetbrains.k2js.translate.reference;
018
019import com.google.dart.compiler.backend.js.ast.JsExpression;
020import com.google.dart.compiler.backend.js.ast.JsInvocation;
021import com.google.dart.compiler.backend.js.ast.JsNameRef;
022import com.google.dart.compiler.backend.js.ast.JsNew;
023import org.jetbrains.annotations.NotNull;
024import org.jetbrains.annotations.Nullable;
025import org.jetbrains.jet.lang.descriptors.*;
026import org.jetbrains.jet.lang.resolve.calls.model.ResolvedCall;
027import org.jetbrains.jet.lang.resolve.calls.model.VariableAsFunctionResolvedCall;
028import org.jetbrains.jet.lang.resolve.calls.util.ExpressionAsFunctionDescriptor;
029import org.jetbrains.jet.lang.resolve.name.Name;
030import org.jetbrains.jet.lang.types.JetType;
031import org.jetbrains.k2js.translate.context.TranslationContext;
032import org.jetbrains.k2js.translate.general.AbstractTranslator;
033import org.jetbrains.k2js.translate.intrinsic.functions.basic.FunctionIntrinsic;
034import org.jetbrains.k2js.translate.intrinsic.functions.patterns.NamePredicate;
035import org.jetbrains.k2js.translate.utils.AnnotationsUtils;
036import org.jetbrains.k2js.translate.utils.ErrorReportingUtils;
037import org.jetbrains.k2js.translate.utils.JsDescriptorUtils;
038
039import java.util.ArrayList;
040import java.util.List;
041
042import static org.jetbrains.k2js.translate.reference.CallParametersResolver.resolveCallParameters;
043import static org.jetbrains.k2js.translate.utils.BindingUtils.isObjectDeclaration;
044import static org.jetbrains.k2js.translate.utils.JsAstUtils.assignment;
045import static org.jetbrains.k2js.translate.utils.JsAstUtils.setQualifier;
046import static org.jetbrains.k2js.translate.utils.JsDescriptorUtils.isConstructorDescriptor;
047
048//TODO: write tests on calling backing fields as functions
049public final class CallTranslator extends AbstractTranslator {
050    @NotNull
051    private final List<JsExpression> arguments;
052    @NotNull
053    private final ResolvedCall<?> resolvedCall;
054    @NotNull
055    private final CallableDescriptor descriptor;
056    @NotNull
057    private final CallType callType;
058    @NotNull
059    private final CallParameters callParameters;
060
061    /*package*/ CallTranslator(@Nullable JsExpression receiver, @Nullable JsExpression callee,
062            @NotNull List<JsExpression> arguments,
063            @NotNull ResolvedCall<? extends CallableDescriptor> resolvedCall,
064            @NotNull CallableDescriptor descriptorToCall,
065            @NotNull CallType callType,
066            @NotNull TranslationContext context) {
067        super(context);
068        this.arguments = arguments;
069        this.resolvedCall = resolvedCall;
070        this.callType = callType;
071        this.descriptor = descriptorToCall;
072        this.callParameters = resolveCallParameters(receiver, callee, descriptor, resolvedCall, context);
073    }
074
075    @NotNull
076        /*package*/ JsExpression translate() {
077        if (isIntrinsic()) {
078            return intrinsicInvocation();
079        }
080        if (isConstructor()) {
081            return constructorCall();
082        }
083        if (isNativeExtensionFunctionCall()) {
084            return nativeExtensionCall();
085        }
086        if (isExtensionFunctionLiteral()) {
087            return extensionFunctionLiteralCall();
088        }
089        if (isExtensionFunction()) {
090            return extensionFunctionCall();
091        }
092        if (isExpressionAsFunction()) {
093            return expressionAsFunctionCall();
094        }
095        if (isInvoke()) {
096            return invokeCall();
097        }
098        return methodCall(getThisObjectOrQualifier());
099    }
100
101    //TODO:
102    private boolean isInvoke() {
103        return descriptor.getName().asString().equals("invoke");
104    }
105
106    @NotNull
107    private JsExpression invokeCall() {
108        JsExpression thisExpression = callParameters.getThisObject();
109        if (thisExpression == null) {
110            return new JsInvocation(callParameters.getFunctionReference(), arguments);
111        }
112        JsInvocation call = new JsInvocation(new JsNameRef("call", callParameters.getFunctionReference()));
113        call.getArguments().add(thisExpression);
114        call.getArguments().addAll(arguments);
115        return call;
116    }
117
118    private boolean isExpressionAsFunction() {
119        return descriptor instanceof ExpressionAsFunctionDescriptor ||
120               resolvedCall instanceof VariableAsFunctionResolvedCall;
121    }
122
123    @NotNull
124    private JsExpression expressionAsFunctionCall() {
125        return methodCall(null);
126    }
127
128    private boolean isIntrinsic() {
129        if (descriptor instanceof FunctionDescriptor) {
130            FunctionIntrinsic intrinsic = context().intrinsics().getFunctionIntrinsics().getIntrinsic((FunctionDescriptor) descriptor);
131            return intrinsic.exists();
132        }
133        return false;
134    }
135
136    @NotNull
137    private JsExpression intrinsicInvocation() {
138        assert descriptor instanceof FunctionDescriptor;
139        try {
140            FunctionIntrinsic intrinsic = context().intrinsics().getFunctionIntrinsics().getIntrinsic((FunctionDescriptor) descriptor);
141            assert intrinsic.exists();
142            return intrinsic.apply(callParameters.getThisOrReceiverOrNull(), arguments, context());
143        }
144        catch (RuntimeException e) {
145            throw ErrorReportingUtils.reportErrorWithLocation(e, descriptor, bindingContext());
146        }
147    }
148
149    private boolean isConstructor() {
150        return isConstructorDescriptor(descriptor);
151    }
152
153    @NotNull
154    private JsExpression constructorCall() {
155        JsExpression constructorReference;
156        ClassDescriptor classDescriptor = (ClassDescriptor) descriptor.getContainingDeclaration();
157        boolean isSet = false;
158        if (AnnotationsUtils.isLibraryObject(classDescriptor) &&
159            (classDescriptor.getName().asString().equals("HashMap") || (isSet = classDescriptor.getName().asString().equals("HashSet")))) {
160            JetType keyType = resolvedCall.getTypeArguments().values().iterator().next();
161            Name keyTypeName = JsDescriptorUtils.getNameIfStandardType(keyType);
162            String collectionClassName;
163            if (keyTypeName != null && (NamePredicate.PRIMITIVE_NUMBERS.apply(keyTypeName) || keyTypeName.asString().equals("String"))) {
164                collectionClassName = isSet ? "PrimitiveHashSet" : "PrimitiveHashMap";
165            }
166            else {
167                collectionClassName = isSet ? "ComplexHashSet" : "ComplexHashMap";
168            }
169
170            constructorReference = context().namer().kotlin(collectionClassName);
171        }
172        else {
173            constructorReference = translateAsFunctionWithNoThisObject(descriptor);
174        }
175
176        return createConstructorCallExpression(constructorReference);
177    }
178
179    @NotNull
180    private JsExpression createConstructorCallExpression(@NotNull JsExpression constructorReference) {
181        if (context().isEcma5() && !AnnotationsUtils.isNativeObject(resolvedCall.getCandidateDescriptor())) {
182            return new JsInvocation(constructorReference, arguments);
183        }
184        else {
185            return new JsNew(constructorReference, arguments);
186        }
187    }
188
189    @NotNull
190    private JsExpression translateAsFunctionWithNoThisObject(@NotNull DeclarationDescriptor descriptor) {
191        return ReferenceTranslator.translateAsFQReference(descriptor, context());
192    }
193
194    private boolean isNativeExtensionFunctionCall() {
195        return AnnotationsUtils.isNativeObject(descriptor) && isExtensionFunction();
196    }
197
198    @NotNull
199    private JsExpression nativeExtensionCall() {
200        return methodCall(callParameters.getReceiver());
201    }
202
203    private boolean isExtensionFunctionLiteral() {
204        boolean isLiteral = isInvoke()
205                            || descriptor instanceof ExpressionAsFunctionDescriptor;
206        return isExtensionFunction() && isLiteral;
207    }
208
209    @NotNull
210    private JsExpression extensionFunctionLiteralCall() {
211        return callType.constructCall(callParameters.getReceiver(), new CallType.CallConstructor() {
212            @NotNull
213            @Override
214            public JsExpression construct(@Nullable JsExpression receiver) {
215                assert receiver != null : "Could not be null for extensions";
216                return constructExtensionLiteralCall(receiver);
217            }
218        }, context());
219    }
220
221    @NotNull
222    private JsExpression constructExtensionLiteralCall(@NotNull JsExpression realReceiver) {
223        List<JsExpression> callArguments = generateExtensionCallArgumentList(realReceiver);
224        return new JsInvocation(new JsNameRef("call", callParameters.getFunctionReference()), callArguments);
225    }
226
227    @SuppressWarnings("UnnecessaryLocalVariable")
228    private boolean isExtensionFunction() {
229        boolean hasReceiver = resolvedCall.getReceiverArgument().exists();
230        return hasReceiver;
231    }
232
233    @NotNull
234    private JsExpression extensionFunctionCall() {
235        return callType.constructCall(callParameters.getReceiver(), new CallType.CallConstructor() {
236            @NotNull
237            @Override
238            public JsExpression construct(@Nullable JsExpression receiver) {
239                assert receiver != null : "Could not be null for extensions";
240                return constructExtensionFunctionCall(receiver);
241            }
242        }, context());
243    }
244
245    @NotNull
246    private JsExpression constructExtensionFunctionCall(@NotNull JsExpression receiver) {
247        List<JsExpression> argumentList = generateExtensionCallArgumentList(receiver);
248        JsExpression functionReference = callParameters.getFunctionReference();
249        setQualifier(functionReference, getThisObjectOrQualifier());
250        return new JsInvocation(functionReference, argumentList);
251    }
252
253    @NotNull
254    private List<JsExpression> generateExtensionCallArgumentList(@NotNull JsExpression receiver) {
255        List<JsExpression> argumentList = new ArrayList<JsExpression>();
256        argumentList.add(receiver);
257        argumentList.addAll(arguments);
258        return argumentList;
259    }
260
261    @NotNull
262    private JsExpression methodCall(@Nullable JsExpression receiver) {
263        return callType.constructCall(receiver, new CallType.CallConstructor() {
264            @NotNull
265            @Override
266            public JsExpression construct(@Nullable JsExpression receiver) {
267                JsExpression qualifiedCallee = getQualifiedCallee(receiver);
268                if (isDirectPropertyAccess()) {
269                    return directPropertyAccess(qualifiedCallee);
270                }
271
272                return new JsInvocation(qualifiedCallee, arguments);
273            }
274        }, context());
275    }
276
277    @NotNull
278    private JsExpression directPropertyAccess(@NotNull JsExpression callee) {
279        if (descriptor instanceof PropertyGetterDescriptor) {
280            assert arguments.isEmpty();
281            return callee;
282        }
283        else {
284            assert descriptor instanceof PropertySetterDescriptor;
285            assert arguments.size() == 1;
286            return assignment(callee, arguments.get(0));
287        }
288    }
289
290    private boolean isDirectPropertyAccess() {
291        return descriptor instanceof PropertyAccessorDescriptor &&
292               (context().isEcma5() || isObjectAccessor((PropertyAccessorDescriptor) descriptor));
293    }
294
295    private boolean isObjectAccessor(@NotNull PropertyAccessorDescriptor propertyAccessorDescriptor) {
296        PropertyDescriptor correspondingProperty = propertyAccessorDescriptor.getCorrespondingProperty();
297        return isObjectDeclaration(bindingContext(), correspondingProperty);
298    }
299
300    @NotNull
301    private JsExpression getQualifiedCallee(@Nullable JsExpression receiver) {
302        JsExpression callee = callParameters.getFunctionReference();
303        if (receiver != null) {
304            setQualifier(callee, receiver);
305        }
306        return callee;
307    }
308
309    @Nullable
310    private JsExpression getThisObjectOrQualifier() {
311        JsExpression thisObject = callParameters.getThisObject();
312        if (thisObject != null) {
313            return thisObject;
314        }
315        return context().getQualifierForDescriptor(descriptor);
316    }
317}