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 extensionFunctionCall(boolean useThis) {
154            return callType.constructCall(callParameters.getReceiver(), new ExtensionCallConstructor(useThis), context());
155        }
156    
157        @NotNull
158        private List<JsExpression> generateCallArgumentList(@NotNull JsExpression receiver) {
159            return TranslationUtils.generateInvocationArguments(receiver, arguments);
160        }
161    
162        @NotNull
163        private JsExpression methodCall(@Nullable JsExpression receiver) {
164            return callType.constructCall(receiver, new CallType.CallConstructor() {
165                @NotNull
166                @Override
167                public JsExpression construct(@Nullable JsExpression receiver) {
168                    JsExpression qualifiedCallee = getQualifiedCallee(receiver);
169                    if (isDirectPropertyAccess()) {
170                        return directPropertyAccess(qualifiedCallee);
171                    }
172    
173                    return new JsInvocation(qualifiedCallee, arguments);
174                }
175            }, context());
176        }
177    
178        @NotNull
179        private JsExpression directPropertyAccess(@NotNull JsExpression callee) {
180            if (descriptor instanceof PropertyGetterDescriptor) {
181                assert arguments.isEmpty();
182                return callee;
183            }
184            else {
185                assert descriptor instanceof PropertySetterDescriptor;
186                assert arguments.size() == 1;
187                return assignment(callee, arguments.get(0));
188            }
189        }
190    
191        private boolean isDirectPropertyAccess() {
192            return descriptor instanceof PropertyAccessorDescriptor &&
193                   (context().isEcma5() || isObjectAccessor((PropertyAccessorDescriptor) descriptor));
194        }
195    
196        private boolean isObjectAccessor(@NotNull PropertyAccessorDescriptor propertyAccessorDescriptor) {
197            PropertyDescriptor correspondingProperty = propertyAccessorDescriptor.getCorrespondingProperty();
198            return isObjectDeclaration(bindingContext(), correspondingProperty);
199        }
200    
201        @NotNull
202        private JsExpression getQualifiedCallee(@Nullable JsExpression receiver) {
203            JsExpression callee = callParameters.getFunctionReference();
204            if (receiver != null) {
205                setQualifier(callee, receiver);
206            }
207            return callee;
208        }
209    
210        @Nullable
211        private JsExpression getThisObjectOrQualifier() {
212            JsExpression thisObject = callParameters.getThisObject();
213            if (thisObject != null) {
214                return thisObject;
215            }
216            return context().getQualifierForDescriptor(descriptor);
217        }
218    
219        private class ExtensionCallConstructor implements CallType.CallConstructor {
220            private final boolean useThis;
221    
222            private ExtensionCallConstructor(boolean useThis) {
223                this.useThis = useThis;
224            }
225    
226            @NotNull
227            @Override
228            public JsExpression construct(@Nullable JsExpression receiver) {
229                assert receiver != null : "Could not be null for extensions";
230                JsExpression functionReference = callParameters.getFunctionReference();
231                if (useThis) {
232                    setQualifier(functionReference, getThisObjectOrQualifier());
233                }
234                return new JsInvocation(callParameters.getFunctionReference(), generateCallArgumentList(receiver));
235            }
236        }
237    }