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 }