001 /*
002 * Copyright 2010-2016 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.kotlin.js.translate.initializer;
018
019 import org.jetbrains.annotations.NotNull;
020 import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
021 import org.jetbrains.kotlin.descriptors.*;
022 import org.jetbrains.kotlin.descriptors.impl.TypeAliasConstructorDescriptor;
023 import org.jetbrains.kotlin.js.backend.ast.*;
024 import org.jetbrains.kotlin.js.translate.callTranslator.CallTranslator;
025 import org.jetbrains.kotlin.js.translate.context.Namer;
026 import org.jetbrains.kotlin.js.translate.context.TranslationContext;
027 import org.jetbrains.kotlin.js.translate.context.UsageTracker;
028 import org.jetbrains.kotlin.js.translate.declaration.DelegationTranslator;
029 import org.jetbrains.kotlin.js.translate.general.AbstractTranslator;
030 import org.jetbrains.kotlin.js.translate.general.Translation;
031 import org.jetbrains.kotlin.js.translate.reference.CallArgumentTranslator;
032 import org.jetbrains.kotlin.js.translate.reference.ReferenceTranslator;
033 import org.jetbrains.kotlin.js.translate.utils.BindingUtils;
034 import org.jetbrains.kotlin.js.translate.utils.JsAstUtils;
035 import org.jetbrains.kotlin.js.translate.utils.JsDescriptorUtils;
036 import org.jetbrains.kotlin.js.translate.utils.jsAstUtils.AstUtilsKt;
037 import org.jetbrains.kotlin.lexer.KtTokens;
038 import org.jetbrains.kotlin.name.Name;
039 import org.jetbrains.kotlin.psi.KtClassOrObject;
040 import org.jetbrains.kotlin.psi.KtEnumEntry;
041 import org.jetbrains.kotlin.psi.KtExpression;
042 import org.jetbrains.kotlin.psi.KtParameter;
043 import org.jetbrains.kotlin.psi.psiUtil.PsiUtilsKt;
044 import org.jetbrains.kotlin.resolve.DescriptorUtils;
045 import org.jetbrains.kotlin.resolve.calls.callUtil.CallUtilKt;
046 import org.jetbrains.kotlin.resolve.calls.model.DefaultValueArgument;
047 import org.jetbrains.kotlin.resolve.calls.model.ExpressionValueArgument;
048 import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall;
049 import org.jetbrains.kotlin.resolve.calls.model.ResolvedValueArgument;
050 import org.jetbrains.kotlin.resolve.descriptorUtil.DescriptorUtilsKt;
051 import org.jetbrains.kotlin.types.typeUtil.TypeUtilsKt;
052
053 import java.util.ArrayList;
054 import java.util.Arrays;
055 import java.util.Collections;
056 import java.util.List;
057
058 import static org.jetbrains.kotlin.js.translate.utils.BindingUtils.*;
059 import static org.jetbrains.kotlin.js.translate.utils.FunctionBodyTranslator.setDefaultValueForArguments;
060 import static org.jetbrains.kotlin.js.translate.utils.JsAstUtils.pureFqn;
061 import static org.jetbrains.kotlin.js.translate.utils.PsiUtils.getPrimaryConstructorParameters;
062
063 public final class ClassInitializerTranslator extends AbstractTranslator {
064 @NotNull
065 private final KtClassOrObject classDeclaration;
066 @NotNull
067 private final JsFunction initFunction;
068 @NotNull
069 private final TranslationContext context;
070 @NotNull
071 private final ClassDescriptor classDescriptor;
072
073 private final ConstructorDescriptor primaryConstructor;
074
075 private int ordinal;
076
077 public ClassInitializerTranslator(
078 @NotNull KtClassOrObject classDeclaration,
079 @NotNull TranslationContext context,
080 @NotNull JsFunction initFunction
081 ) {
082 super(context);
083 this.classDeclaration = classDeclaration;
084 this.initFunction = initFunction;
085 this.context = context.contextWithScope(initFunction);
086 classDescriptor = BindingUtils.getClassDescriptor(bindingContext(), classDeclaration);
087 primaryConstructor = classDescriptor.getUnsubstitutedPrimaryConstructor();
088 }
089
090 public void setOrdinal(int ordinal) {
091 this.ordinal = ordinal;
092 }
093
094 @NotNull
095 @Override
096 protected TranslationContext context() {
097 return context;
098 }
099
100 public void generateInitializeMethod(DelegationTranslator delegationTranslator) {
101 addOuterClassReference(classDescriptor);
102
103 if (primaryConstructor != null) {
104 initFunction.getBody().getStatements().addAll(setDefaultValueForArguments(primaryConstructor, context()));
105
106 mayBeAddCallToSuperMethod(initFunction);
107
108 //NOTE: while we translate constructor parameters we also add property initializer statements
109 // for properties declared as constructor parameters
110 initFunction.getParameters().addAll(translatePrimaryConstructorParameters());
111
112 // Initialize enum 'name' and 'ordinal' before translating property initializers.
113 if (classDescriptor.getKind() == ClassKind.ENUM_CLASS) {
114 addEnumClassParameters(initFunction);
115 }
116 }
117
118 addThrowableCall();
119
120 delegationTranslator.addInitCode(initFunction.getBody().getStatements());
121 new InitializerVisitor().traverseContainer(classDeclaration, context().innerBlock(initFunction.getBody()));
122 }
123
124 private static void addEnumClassParameters(JsFunction constructorFunction) {
125 JsName nameParamName = constructorFunction.getScope().declareFreshName("name");
126 JsName ordinalParamName = constructorFunction.getScope().declareFreshName("ordinal");
127 constructorFunction.getParameters().addAll(0, Arrays.asList(new JsParameter(nameParamName), new JsParameter(ordinalParamName)));
128
129 constructorFunction.getBody().getStatements().add(JsAstUtils.assignmentToThisField(Namer.ENUM_NAME_FIELD, nameParamName.makeRef()));
130 constructorFunction.getBody().getStatements().add(JsAstUtils.assignmentToThisField(Namer.ENUM_ORDINAL_FIELD, ordinalParamName.makeRef()));
131 }
132
133 private void addOuterClassReference(ClassDescriptor classDescriptor) {
134 JsName outerName = context.getOuterClassReference(classDescriptor);
135 if (outerName == null) return;
136
137 initFunction.getParameters().add(0, new JsParameter(outerName));
138
139 JsExpression paramRef = pureFqn(outerName, null);
140 JsExpression assignment = JsAstUtils.assignment(pureFqn(outerName, JsLiteral.THIS), paramRef);
141 initFunction.getBody().getStatements().add(new JsExpressionStatement(assignment));
142 }
143
144 @NotNull
145 public static JsExpression generateEnumEntryInstanceCreation(
146 @NotNull TranslationContext context,
147 @NotNull KtEnumEntry enumEntry,
148 int ordinal
149 ) {
150 ResolvedCall<? extends FunctionDescriptor> resolvedCall = getSuperCall(context.bindingContext(), enumEntry);
151 if (resolvedCall == null) {
152 assert enumEntry.getInitializerList() == null : "Super call is missing on an enum entry with explicit initializer list " +
153 PsiUtilsKt.getTextWithLocation(enumEntry);
154 resolvedCall = CallUtilKt.getFunctionResolvedCallWithAssert(enumEntry, context.bindingContext());
155 }
156
157 JsExpression nameArg = context.program().getStringLiteral(enumEntry.getName());
158 JsExpression ordinalArg = context.program().getNumberLiteral(ordinal);
159 List<JsExpression> additionalArgs = Arrays.asList(nameArg, ordinalArg);
160
161 JsExpression call = CallTranslator.translate(context, resolvedCall);
162 if (call instanceof JsInvocation) {
163 JsInvocation invocation = (JsInvocation) call;
164 invocation.getArguments().addAll(0, additionalArgs);
165 }
166 else if (call instanceof JsNew) {
167 JsNew invocation = (JsNew) call;
168 invocation.getArguments().addAll(0, additionalArgs);
169 }
170
171 return call;
172 }
173
174 private void mayBeAddCallToSuperMethod(JsFunction initializer) {
175 if (classDeclaration.hasModifier(KtTokens.ENUM_KEYWORD)) {
176 addCallToSuperMethod(Collections.<JsExpression>emptyList(), initializer);
177 }
178 else if (hasAncestorClass(bindingContext(), classDeclaration)) {
179 ResolvedCall<FunctionDescriptor> superCall = getSuperCall(bindingContext(), classDeclaration);
180
181 if (superCall == null) {
182 if (DescriptorUtils.isEnumEntry(classDescriptor)) {
183 addCallToSuperMethod(getAdditionalArgumentsForEnumConstructor(), initializer);
184 }
185 return;
186 }
187
188 if (JsDescriptorUtils.isImmediateSubtypeOfError(classDescriptor)) {
189 emulateSuperCallToNativeError(context, classDescriptor, superCall, JsLiteral.THIS);
190 return;
191 }
192
193 if (classDeclaration instanceof KtEnumEntry) {
194 JsExpression expression = CallTranslator.translate(context(), superCall, null);
195
196 JsExpression fixedInvocation = AstUtilsKt.toInvocationWith(
197 expression, getAdditionalArgumentsForEnumConstructor(), 0, JsLiteral.THIS);
198 initFunction.getBody().getStatements().add(fixedInvocation.makeStmt());
199 }
200 else {
201 List<JsExpression> arguments = new ArrayList<JsExpression>();
202
203 ConstructorDescriptor superDescriptor = (ConstructorDescriptor) superCall.getResultingDescriptor();
204 if (superDescriptor instanceof TypeAliasConstructorDescriptor) {
205 superDescriptor = ((TypeAliasConstructorDescriptor) superDescriptor).getUnderlyingConstructorDescriptor();
206 }
207
208 List<DeclarationDescriptor> superclassClosure = context.getClassOrConstructorClosure(superDescriptor);
209 if (superclassClosure != null) {
210 UsageTracker tracker = context.usageTracker();
211 if (tracker != null) {
212 for (DeclarationDescriptor capturedValue : superclassClosure) {
213 tracker.used(capturedValue);
214 arguments.add(tracker.getCapturedDescriptorToJsName().get(capturedValue).makeRef());
215 }
216 }
217 }
218
219 if (superCall.getDispatchReceiver() != null) {
220 JsExpression receiver = context.getDispatchReceiver(JsDescriptorUtils.getReceiverParameterForReceiver(
221 superCall.getDispatchReceiver()));
222 arguments.add(receiver);
223 }
224
225 if (!DescriptorUtils.isAnonymousObject(classDescriptor)) {
226 arguments.addAll(CallArgumentTranslator.translate(superCall, null, context()).getTranslateArguments());
227 }
228 else {
229 for (ValueParameterDescriptor parameter : superDescriptor.getValueParameters()) {
230 JsName parameterName = context.getNameForDescriptor(parameter);
231 arguments.add(parameterName.makeRef());
232 initializer.getParameters().add(new JsParameter(parameterName));
233 }
234 }
235
236 if (superDescriptor.isPrimary()) {
237 addCallToSuperMethod(arguments, initializer);
238 }
239 else {
240 int maxValueArgumentIndex = 0;
241 for (ValueParameterDescriptor arg : superCall.getValueArguments().keySet()) {
242 ResolvedValueArgument resolvedArg = superCall.getValueArguments().get(arg);
243 if (!(resolvedArg instanceof DefaultValueArgument)) {
244 maxValueArgumentIndex = Math.max(maxValueArgumentIndex, arg.getIndex() + 1);
245 }
246 }
247 int padSize = superDescriptor.getValueParameters().size() - maxValueArgumentIndex;
248 while (padSize-- > 0) {
249 arguments.add(Namer.getUndefinedExpression());
250 }
251 addCallToSuperSecondaryConstructor(arguments, superDescriptor);
252 }
253 }
254 }
255 }
256
257 public static void emulateSuperCallToNativeError(
258 @NotNull TranslationContext context,
259 @NotNull ClassDescriptor classDescriptor,
260 @NotNull ResolvedCall<? extends FunctionDescriptor> superCall,
261 @NotNull JsExpression receiver
262 ) {
263 ClassDescriptor superClass = DescriptorUtilsKt.getSuperClassOrAny(classDescriptor);
264 JsExpression superClassRef = ReferenceTranslator.translateAsTypeReference(superClass, context);
265 JsExpression superInvocation = new JsInvocation(Namer.getFunctionCallRef(superClassRef), receiver.deepCopy());
266 List<JsStatement> statements = context.getCurrentBlock().getStatements();
267 statements.add(JsAstUtils.asSyntheticStatement(superInvocation));
268
269 JsExpression messageArgument = Namer.getUndefinedExpression();
270 JsExpression causeArgument = JsLiteral.NULL;
271 for (ValueParameterDescriptor param : superCall.getResultingDescriptor().getValueParameters()) {
272 ResolvedValueArgument argument = superCall.getValueArguments().get(param);
273 if (!(argument instanceof ExpressionValueArgument)) continue;
274
275 ExpressionValueArgument exprArgument = (ExpressionValueArgument) argument;
276 assert exprArgument.getValueArgument() != null;
277
278 KtExpression value = exprArgument.getValueArgument().getArgumentExpression();
279 assert value != null;
280 JsExpression jsValue = Translation.translateAsExpression(value, context);
281
282 if (KotlinBuiltIns.isStringOrNullableString(param.getType())) {
283 messageArgument = context.cacheExpressionIfNeeded(jsValue);
284 }
285 else if (TypeUtilsKt.isConstructedFromClassWithGivenFqName(param.getType(), KotlinBuiltIns.FQ_NAMES.throwable)) {
286 causeArgument = context.cacheExpressionIfNeeded(jsValue);
287 }
288 else {
289 statements.add(JsAstUtils.asSyntheticStatement(jsValue));
290 }
291 }
292
293 PropertyDescriptor messageProperty = DescriptorUtils.getPropertyByName(
294 classDescriptor.getUnsubstitutedMemberScope(), Name.identifier("message"));
295 JsExpression messageRef = pureFqn(context.getNameForBackingField(messageProperty), receiver.deepCopy());
296 JsExpression messageIsUndefined = JsAstUtils.typeOfIs(messageArgument, context.program().getStringLiteral("undefined"));
297 JsExpression causeIsNull = new JsBinaryOperation(JsBinaryOperator.NEQ, causeArgument, JsLiteral.NULL);
298 JsExpression causeToStringCond = JsAstUtils.and(messageIsUndefined, causeIsNull);
299 JsExpression causeToString = new JsInvocation(pureFqn("toString", Namer.kotlinObject()), causeArgument.deepCopy());
300
301 JsExpression correctedMessage;
302 if (causeArgument == JsLiteral.NULL) {
303 correctedMessage = messageArgument.deepCopy();
304 }
305 else {
306 if (JsAstUtils.isUndefinedExpression(messageArgument)) {
307 causeToStringCond = causeIsNull;
308 }
309 correctedMessage = new JsConditional(causeToStringCond, causeToString, messageArgument);
310 }
311
312 statements.add(JsAstUtils.asSyntheticStatement(JsAstUtils.assignment(messageRef, correctedMessage)));
313
314 PropertyDescriptor causeProperty = DescriptorUtils.getPropertyByName(
315 classDescriptor.getUnsubstitutedMemberScope(), Name.identifier("cause"));
316 JsExpression causeRef = pureFqn(context.getNameForBackingField(causeProperty), receiver.deepCopy());
317 statements.add(JsAstUtils.asSyntheticStatement(JsAstUtils.assignment(causeRef, causeArgument.deepCopy())));
318 }
319
320 @NotNull
321 private List<JsExpression> getAdditionalArgumentsForEnumConstructor() {
322 List<JsExpression> additionalArguments = new ArrayList<JsExpression>();
323 additionalArguments.add(program().getStringLiteral(classDescriptor.getName().asString()));
324 additionalArguments.add(program().getNumberLiteral(ordinal));
325 return additionalArguments;
326 }
327
328 private void addCallToSuperMethod(@NotNull List<JsExpression> arguments, @NotNull JsFunction initializer) {
329 if (initializer.getName() == null) {
330 JsName ref = context().scope().declareName(Namer.CALLEE_NAME);
331 initializer.setName(ref);
332 }
333
334 ClassDescriptor superclassDescriptor = DescriptorUtilsKt.getSuperClassOrAny(classDescriptor);
335 JsExpression superConstructorRef = context().getInnerReference(superclassDescriptor);
336 JsInvocation call = new JsInvocation(Namer.getFunctionCallRef(superConstructorRef));
337 call.getArguments().add(JsLiteral.THIS);
338 call.getArguments().addAll(arguments);
339 initFunction.getBody().getStatements().add(call.makeStmt());
340 }
341
342 private void addCallToSuperSecondaryConstructor(@NotNull List<JsExpression> arguments, @NotNull ConstructorDescriptor descriptor) {
343 JsExpression reference = context.getInnerReference(descriptor);
344 JsInvocation call = new JsInvocation(reference);
345 call.getArguments().addAll(arguments);
346 call.getArguments().add(JsLiteral.THIS);
347 initFunction.getBody().getStatements().add(call.makeStmt());
348 }
349
350 @NotNull
351 private List<JsParameter> translatePrimaryConstructorParameters() {
352 List<KtParameter> parameterList = getPrimaryConstructorParameters(classDeclaration);
353 List<JsParameter> result = new ArrayList<JsParameter>();
354 for (KtParameter jetParameter : parameterList) {
355 result.add(translateParameter(jetParameter));
356 }
357 return result;
358 }
359
360 @NotNull
361 private JsParameter translateParameter(@NotNull KtParameter jetParameter) {
362 DeclarationDescriptor parameterDescriptor = getDescriptorForElement(bindingContext(), jetParameter);
363 JsName parameterName = context().getNameForDescriptor(parameterDescriptor);
364 JsParameter jsParameter = new JsParameter(parameterName);
365 mayBeAddInitializerStatementForProperty(jsParameter, jetParameter);
366 return jsParameter;
367 }
368
369 private void mayBeAddInitializerStatementForProperty(@NotNull JsParameter jsParameter,
370 @NotNull KtParameter jetParameter) {
371 PropertyDescriptor propertyDescriptor = getPropertyDescriptorForConstructorParameter(bindingContext(), jetParameter);
372 if (propertyDescriptor == null) {
373 return;
374 }
375 JsNameRef initialValueForProperty = jsParameter.getName().makeRef();
376 addInitializerOrPropertyDefinition(initialValueForProperty, propertyDescriptor);
377 }
378
379 private void addInitializerOrPropertyDefinition(@NotNull JsNameRef initialValue, @NotNull PropertyDescriptor propertyDescriptor) {
380 initFunction.getBody().getStatements().add(
381 InitializerUtils.generateInitializerForProperty(context(), propertyDescriptor, initialValue));
382 }
383
384 private void addThrowableCall() {
385 if (!JsDescriptorUtils.isExceptionClass(classDescriptor)) return;
386
387 if (JsDescriptorUtils.isImmediateSubtypeOfError(classDescriptor)) {
388 ClassDescriptor superClass = DescriptorUtilsKt.getSuperClassOrAny(classDescriptor);
389 JsExpression invocation = new JsInvocation(
390 pureFqn("captureStack", Namer.kotlinObject()),
391 ReferenceTranslator.translateAsTypeReference(superClass, context()),
392 JsLiteral.THIS);
393 initFunction.getBody().getStatements().add(JsAstUtils.asSyntheticStatement(invocation));
394 }
395
396 JsExpression nameLiteral = context.program().getStringLiteral(context.getInnerNameForDescriptor(classDescriptor).getIdent());
397 JsExpression nameAssignment = JsAstUtils.assignment(pureFqn("name", JsLiteral.THIS), nameLiteral);
398 initFunction.getBody().getStatements().add(JsAstUtils.asSyntheticStatement(nameAssignment));
399 }
400 }