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.HasArguments;
020    import com.google.dart.compiler.backend.js.ast.JsExpression;
021    import com.google.dart.compiler.backend.js.ast.JsInvocation;
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.k2js.translate.context.TranslationContext;
030    import org.jetbrains.k2js.translate.general.AbstractTranslator;
031    import org.jetbrains.k2js.translate.intrinsic.functions.basic.FunctionIntrinsic;
032    import org.jetbrains.k2js.translate.utils.AnnotationsUtils;
033    import org.jetbrains.k2js.translate.utils.ErrorReportingUtils;
034    import org.jetbrains.k2js.translate.utils.TranslationUtils;
035    
036    import java.util.List;
037    
038    import static org.jetbrains.k2js.translate.reference.CallParametersResolver.resolveCallParameters;
039    import static org.jetbrains.k2js.translate.utils.BindingUtils.isObjectDeclaration;
040    import static org.jetbrains.k2js.translate.utils.JsAstUtils.assignment;
041    import static org.jetbrains.k2js.translate.utils.JsAstUtils.setQualifier;
042    import static org.jetbrains.k2js.translate.utils.JsDescriptorUtils.isConstructorDescriptor;
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 (isConstructor()) {
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            return methodCall(getThisObjectOrQualifier());
100        }
101    
102        private boolean isExpressionAsFunction() {
103            return descriptor instanceof ExpressionAsFunctionDescriptor ||
104                   resolvedCall instanceof VariableAsFunctionResolvedCall;
105        }
106    
107        @NotNull
108        private JsExpression expressionAsFunctionCall() {
109            return methodCall(null);
110        }
111    
112        @Nullable
113        private JsExpression intrinsicInvocation() {
114            if (descriptor instanceof FunctionDescriptor) {
115                try {
116                    FunctionIntrinsic intrinsic = context().intrinsics().getFunctionIntrinsics().getIntrinsic((FunctionDescriptor) descriptor);
117                    if (intrinsic.exists()) {
118                        return intrinsic.apply(this, arguments, context());
119                    }
120                }
121                catch (RuntimeException e) {
122                    throw ErrorReportingUtils.reportErrorWithLocation(e, descriptor, bindingContext());
123                }
124            }
125            return null;
126        }
127    
128        private boolean isConstructor() {
129            return isConstructorDescriptor(descriptor);
130        }
131    
132        @NotNull
133        public HasArguments createConstructorCallExpression(@NotNull JsExpression constructorReference) {
134            if (context().isEcma5() && !AnnotationsUtils.isNativeObject(resolvedCall.getCandidateDescriptor())) {
135                return new JsInvocation(constructorReference, arguments);
136            }
137            else {
138                return new JsNew(constructorReference, arguments);
139            }
140        }
141    
142        @NotNull
143        private JsExpression translateAsFunctionWithNoThisObject(@NotNull DeclarationDescriptor descriptor) {
144            return ReferenceTranslator.translateAsFQReference(descriptor, context());
145        }
146    
147        @NotNull
148        private JsExpression nativeExtensionCall() {
149            return methodCall(callParameters.getReceiver());
150        }
151    
152        @NotNull
153        public JsExpression explicitInvokeCall() {
154            return callType.constructCall(callParameters.getThisObject(), new CallType.CallConstructor() {
155                @NotNull
156                @Override
157                public JsExpression construct(@Nullable JsExpression receiver) {
158                    return new JsInvocation(receiver, arguments);
159                }
160            }, context());
161        }
162    
163        @NotNull
164        public JsExpression extensionFunctionCall(final boolean useThis) {
165            return callType.constructCall(callParameters.getReceiver(), new CallType.CallConstructor() {
166                @NotNull
167                @Override
168                public JsExpression construct(@Nullable JsExpression receiver) {
169                    assert receiver != null : "Could not be null for extensions";
170                    JsExpression functionReference = callParameters.getFunctionReference();
171                    if (useThis) {
172                        setQualifier(functionReference, getThisObjectOrQualifier());
173                    }
174                    return new JsInvocation(callParameters.getFunctionReference(), generateCallArgumentList(receiver));
175                }
176            }, context());
177        }
178    
179        @NotNull
180        private List<JsExpression> generateCallArgumentList(@NotNull JsExpression receiver) {
181            return TranslationUtils.generateInvocationArguments(receiver, arguments);
182        }
183    
184        @NotNull
185        private JsExpression methodCall(@Nullable JsExpression receiver) {
186            return callType.constructCall(receiver, new CallType.CallConstructor() {
187                @NotNull
188                @Override
189                public JsExpression construct(@Nullable JsExpression receiver) {
190                    JsExpression qualifiedCallee = getQualifiedCallee(receiver);
191                    if (isDirectPropertyAccess()) {
192                        return directPropertyAccess(qualifiedCallee);
193                    }
194    
195                    return new JsInvocation(qualifiedCallee, arguments);
196                }
197            }, context());
198        }
199    
200        @NotNull
201        private JsExpression directPropertyAccess(@NotNull JsExpression callee) {
202            if (descriptor instanceof PropertyGetterDescriptor) {
203                assert arguments.isEmpty();
204                return callee;
205            }
206            else {
207                assert descriptor instanceof PropertySetterDescriptor;
208                assert arguments.size() == 1;
209                return assignment(callee, arguments.get(0));
210            }
211        }
212    
213        private boolean isDirectPropertyAccess() {
214            return descriptor instanceof PropertyAccessorDescriptor &&
215                   (context().isEcma5() || isObjectAccessor((PropertyAccessorDescriptor) descriptor));
216        }
217    
218        private boolean isObjectAccessor(@NotNull PropertyAccessorDescriptor propertyAccessorDescriptor) {
219            PropertyDescriptor correspondingProperty = propertyAccessorDescriptor.getCorrespondingProperty();
220            return isObjectDeclaration(correspondingProperty);
221        }
222    
223        @NotNull
224        private JsExpression getQualifiedCallee(@Nullable JsExpression receiver) {
225            JsExpression callee = callParameters.getFunctionReference();
226            if (receiver != null) {
227                setQualifier(callee, receiver);
228            }
229            return callee;
230        }
231    
232        @Nullable
233        private JsExpression getThisObjectOrQualifier() {
234            JsExpression thisObject = callParameters.getThisObject();
235            if (thisObject != null) {
236                return thisObject;
237            }
238            return context().getQualifierForDescriptor(descriptor);
239        }
240    }