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