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 com.google.dart.compiler.backend.js.JsReservedIdentifiers;
020    import com.google.dart.compiler.backend.js.ast.*;
021    import org.jetbrains.annotations.NotNull;
022    import org.jetbrains.kotlin.descriptors.*;
023    import org.jetbrains.kotlin.js.translate.callTranslator.CallTranslator;
024    import org.jetbrains.kotlin.js.translate.context.Namer;
025    import org.jetbrains.kotlin.js.translate.context.TranslationContext;
026    import org.jetbrains.kotlin.js.translate.context.UsageTracker;
027    import org.jetbrains.kotlin.js.translate.declaration.DelegationTranslator;
028    import org.jetbrains.kotlin.js.translate.general.AbstractTranslator;
029    import org.jetbrains.kotlin.js.translate.reference.CallArgumentTranslator;
030    import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils;
031    import org.jetbrains.kotlin.js.translate.utils.JsAstUtils;
032    import org.jetbrains.kotlin.js.translate.utils.jsAstUtils.AstUtilsKt;
033    import org.jetbrains.kotlin.lexer.KtTokens;
034    import org.jetbrains.kotlin.name.Name;
035    import org.jetbrains.kotlin.psi.KtClassOrObject;
036    import org.jetbrains.kotlin.psi.KtEnumEntry;
037    import org.jetbrains.kotlin.psi.KtParameter;
038    import org.jetbrains.kotlin.resolve.DescriptorUtils;
039    import org.jetbrains.kotlin.resolve.calls.model.DefaultValueArgument;
040    import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall;
041    import org.jetbrains.kotlin.resolve.calls.model.ResolvedValueArgument;
042    import org.jetbrains.kotlin.types.KotlinType;
043    
044    import java.util.ArrayList;
045    import java.util.Collections;
046    import java.util.List;
047    
048    import static org.jetbrains.kotlin.js.translate.utils.BindingUtils.*;
049    import static org.jetbrains.kotlin.js.translate.utils.FunctionBodyTranslator.setDefaultValueForArguments;
050    import static org.jetbrains.kotlin.js.translate.utils.JsAstUtils.pureFqn;
051    import static org.jetbrains.kotlin.js.translate.utils.PsiUtils.getPrimaryConstructorParameters;
052    import static org.jetbrains.kotlin.resolve.DescriptorUtils.getClassDescriptorForType;
053    
054    public final class ClassInitializerTranslator extends AbstractTranslator {
055        @NotNull
056        private final KtClassOrObject classDeclaration;
057        @NotNull
058        private final JsFunction initFunction;
059        private final TranslationContext context;
060    
061        public ClassInitializerTranslator(
062                @NotNull KtClassOrObject classDeclaration,
063                @NotNull TranslationContext context
064        ) {
065            super(context);
066            this.classDeclaration = classDeclaration;
067            this.initFunction = createInitFunction(classDeclaration, context);
068            this.context = context.contextWithScope(initFunction);
069        }
070    
071        @NotNull
072        @Override
073        protected TranslationContext context() {
074            return context;
075        }
076    
077        @NotNull
078        private static JsFunction createInitFunction(KtClassOrObject declaration, TranslationContext context) {
079            //TODO: it's inconsistent that we have scope for class and function for constructor, currently have problems implementing better way
080            ClassDescriptor classDescriptor = getClassDescriptor(context.bindingContext(), declaration);
081            ConstructorDescriptor primaryConstructor = classDescriptor.getUnsubstitutedPrimaryConstructor();
082    
083            String functionName = AnnotationsUtils.getJsName(classDescriptor);
084            if (functionName == null) {
085                Name name = classDescriptor.getName();
086                if (!name.isSpecial()) {
087                    functionName = name.asString();
088                }
089            }
090    
091            JsFunction ctorFunction;
092            if (primaryConstructor != null) {
093                ctorFunction = context.getFunctionObject(primaryConstructor);
094            }
095            else {
096                ctorFunction = new JsFunction(context.scope(), new JsBlock(), "fake constructor for " + functionName);
097            }
098    
099            if (functionName != null) {
100                if (JsReservedIdentifiers.reservedGlobalSymbols.contains(functionName) ||
101                    JsFunctionScope.Companion.getRESERVED_WORDS().contains(functionName)
102                ) {
103                    functionName += "$";
104                }
105                ctorFunction.setName(ctorFunction.getScope().declareName(functionName));
106            }
107    
108            return ctorFunction;
109        }
110    
111        @NotNull
112        public JsFunction generateInitializeMethod(DelegationTranslator delegationTranslator) {
113            ClassDescriptor classDescriptor = getClassDescriptor(bindingContext(), classDeclaration);
114            addOuterClassReference(classDescriptor);
115            ConstructorDescriptor primaryConstructor = classDescriptor.getUnsubstitutedPrimaryConstructor();
116    
117            if (primaryConstructor != null) {
118                initFunction.getBody().getStatements().addAll(setDefaultValueForArguments(primaryConstructor, context()));
119    
120                mayBeAddCallToSuperMethod(initFunction, classDescriptor);
121    
122                //NOTE: while we translate constructor parameters we also add property initializer statements
123                // for properties declared as constructor parameters
124                initFunction.getParameters().addAll(translatePrimaryConstructorParameters());
125            }
126    
127            delegationTranslator.addInitCode(initFunction.getBody().getStatements());
128            new InitializerVisitor().traverseContainer(classDeclaration, context().innerBlock(initFunction.getBody()));
129    
130            return initFunction;
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 JsExpression generateEnumEntryInstanceCreation(@NotNull KotlinType enumClassType) {
146            ResolvedCall<FunctionDescriptor> superCall = getSuperCall(bindingContext(), classDeclaration);
147    
148            if (superCall == null) {
149                ClassDescriptor classDescriptor = getClassDescriptorForType(enumClassType);
150                JsNameRef reference = context().getQualifiedReference(classDescriptor);
151                return new JsNew(reference);
152            }
153    
154            return CallTranslator.translate(context(), superCall);
155        }
156    
157        private void mayBeAddCallToSuperMethod(JsFunction initializer, @NotNull ClassDescriptor descriptor) {
158            if (classDeclaration.hasModifier(KtTokens.ENUM_KEYWORD)) {
159                addCallToSuperMethod(Collections.<JsExpression>emptyList(), initializer);
160            }
161            else if (hasAncestorClass(bindingContext(), classDeclaration)) {
162                ResolvedCall<FunctionDescriptor> superCall = getSuperCall(bindingContext(), classDeclaration);
163                if (superCall == null) {
164                    if (DescriptorUtils.isEnumEntry(descriptor)) {
165                        addCallToSuperMethod(Collections.<JsExpression>emptyList(), initializer);
166                    }
167                    return;
168                }
169    
170                if (classDeclaration instanceof KtEnumEntry) {
171                    JsExpression expression = CallTranslator.translate(context(), superCall, null);
172                    JsExpression fixedInvocation = AstUtilsKt.toInvocationWith(expression, Collections.<JsExpression>emptyList(), 0,
173                                                                               JsLiteral.THIS);
174                    initFunction.getBody().getStatements().add(fixedInvocation.makeStmt());
175                }
176                else {
177                    List<JsExpression> arguments = new ArrayList<JsExpression>();
178    
179                    ConstructorDescriptor superDescriptor = (ConstructorDescriptor) superCall.getResultingDescriptor();
180    
181                    List<DeclarationDescriptor> superclassClosure = context.getClassOrConstructorClosure(superDescriptor);
182                    if (superclassClosure != null) {
183                        UsageTracker tracker = context.usageTracker();
184                        assert tracker != null : "Closure exists, therefore UsageTracker must exist too. Translating constructor of " +
185                                                 descriptor;
186                        for (DeclarationDescriptor capturedValue : superclassClosure) {
187                            tracker.used(capturedValue);
188                            arguments.add(tracker.getCapturedDescriptorToJsName().get(capturedValue).makeRef());
189                        }
190                    }
191    
192                    if (superDescriptor.getContainingDeclaration().isInner() && descriptor.isInner()) {
193                        arguments.add(pureFqn(Namer.OUTER_FIELD_NAME, JsLiteral.THIS));
194                    }
195    
196                    if (!DescriptorUtils.isAnonymousObject(descriptor)) {
197                        arguments.addAll(CallArgumentTranslator.translate(superCall, null, context()).getTranslateArguments());
198                    }
199                    else {
200                        for (ValueParameterDescriptor parameter : superDescriptor.getValueParameters()) {
201                            JsName parameterName = context.getNameForDescriptor(parameter);
202                            arguments.add(parameterName.makeRef());
203                            initializer.getParameters().add(new JsParameter(parameterName));
204                        }
205                    }
206    
207                    if (superDescriptor.isPrimary()) {
208                        addCallToSuperMethod(arguments, initializer);
209                    }
210                    else {
211                        int maxValueArgumentIndex = 0;
212                        for (ValueParameterDescriptor arg : superCall.getValueArguments().keySet()) {
213                            ResolvedValueArgument resolvedArg = superCall.getValueArguments().get(arg);
214                            if (!(resolvedArg instanceof DefaultValueArgument)) {
215                                maxValueArgumentIndex = Math.max(maxValueArgumentIndex, arg.getIndex() + 1);
216                            }
217                        }
218                        int padSize = superDescriptor.getValueParameters().size() - maxValueArgumentIndex;
219                        while (padSize-- > 0) {
220                            arguments.add(Namer.getUndefinedExpression());
221                        }
222                        addCallToSuperSecondaryConstructor(arguments, superDescriptor);
223                    }
224                }
225            }
226        }
227    
228        private void addCallToSuperMethod(@NotNull List<JsExpression> arguments, JsFunction initializer) {
229            if (initializer.getName() == null) {
230                JsName ref = context().scope().declareName(Namer.CALLEE_NAME);
231                initializer.setName(ref);
232            }
233    
234            JsInvocation call = new JsInvocation(Namer.getFunctionCallRef(Namer.superMethodNameRef(initializer.getName())));
235            call.getArguments().add(JsLiteral.THIS);
236            call.getArguments().addAll(arguments);
237            initFunction.getBody().getStatements().add(call.makeStmt());
238        }
239    
240        private void addCallToSuperSecondaryConstructor(@NotNull List<JsExpression> arguments, @NotNull ConstructorDescriptor descriptor) {
241            JsExpression reference = context.getQualifiedReference(descriptor);
242            JsInvocation call = new JsInvocation(reference);
243            call.getArguments().addAll(arguments);
244            call.getArguments().add(JsLiteral.THIS);
245            initFunction.getBody().getStatements().add(call.makeStmt());
246        }
247    
248        @NotNull
249        private List<JsParameter> translatePrimaryConstructorParameters() {
250            List<KtParameter> parameterList = getPrimaryConstructorParameters(classDeclaration);
251            List<JsParameter> result = new ArrayList<JsParameter>();
252            for (KtParameter jetParameter : parameterList) {
253                result.add(translateParameter(jetParameter));
254            }
255            return result;
256        }
257    
258        @NotNull
259        private JsParameter translateParameter(@NotNull KtParameter jetParameter) {
260            DeclarationDescriptor parameterDescriptor =
261                    getDescriptorForElement(bindingContext(), jetParameter);
262            JsName parameterName = context().getNameForDescriptor(parameterDescriptor);
263            JsParameter jsParameter = new JsParameter(parameterName);
264            mayBeAddInitializerStatementForProperty(jsParameter, jetParameter);
265            return jsParameter;
266        }
267    
268        private void mayBeAddInitializerStatementForProperty(@NotNull JsParameter jsParameter,
269                @NotNull KtParameter jetParameter) {
270            PropertyDescriptor propertyDescriptor =
271                    getPropertyDescriptorForConstructorParameter(bindingContext(), jetParameter);
272            if (propertyDescriptor == null) {
273                return;
274            }
275            JsNameRef initialValueForProperty = jsParameter.getName().makeRef();
276            addInitializerOrPropertyDefinition(initialValueForProperty, propertyDescriptor);
277        }
278    
279        private void addInitializerOrPropertyDefinition(@NotNull JsNameRef initialValue, @NotNull PropertyDescriptor propertyDescriptor) {
280            initFunction.getBody().getStatements().add(
281                    InitializerUtils.generateInitializerForProperty(context(), propertyDescriptor, initialValue));
282        }
283    }