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 }