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    }