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.JsExpression;
020    import com.google.dart.compiler.backend.js.ast.JsInvocation;
021    import com.google.dart.compiler.backend.js.ast.JsNameRef;
022    import com.google.dart.compiler.backend.js.ast.JsNew;
023    import org.jetbrains.annotations.NotNull;
024    import org.jetbrains.annotations.Nullable;
025    import org.jetbrains.jet.lang.descriptors.*;
026    import org.jetbrains.jet.lang.resolve.calls.model.ResolvedCall;
027    import org.jetbrains.jet.lang.resolve.calls.model.VariableAsFunctionResolvedCall;
028    import org.jetbrains.jet.lang.resolve.calls.util.ExpressionAsFunctionDescriptor;
029    import org.jetbrains.jet.lang.resolve.name.Name;
030    import org.jetbrains.jet.lang.types.JetType;
031    import org.jetbrains.k2js.translate.context.TranslationContext;
032    import org.jetbrains.k2js.translate.general.AbstractTranslator;
033    import org.jetbrains.k2js.translate.intrinsic.functions.basic.FunctionIntrinsic;
034    import org.jetbrains.k2js.translate.intrinsic.functions.patterns.NamePredicate;
035    import org.jetbrains.k2js.translate.utils.AnnotationsUtils;
036    import org.jetbrains.k2js.translate.utils.ErrorReportingUtils;
037    import org.jetbrains.k2js.translate.utils.JsDescriptorUtils;
038    
039    import java.util.ArrayList;
040    import java.util.List;
041    
042    import static org.jetbrains.k2js.translate.reference.CallParametersResolver.resolveCallParameters;
043    import static org.jetbrains.k2js.translate.utils.BindingUtils.isObjectDeclaration;
044    import static org.jetbrains.k2js.translate.utils.JsAstUtils.assignment;
045    import static org.jetbrains.k2js.translate.utils.JsAstUtils.setQualifier;
046    import static org.jetbrains.k2js.translate.utils.JsDescriptorUtils.isConstructorDescriptor;
047    
048    //TODO: write tests on calling backing fields as functions
049    public 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    }