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 }