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.ast.*;
020    import com.intellij.util.SmartList;
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.JsAstUtils;
031    import org.jetbrains.kotlin.js.translate.utils.jsAstUtils.AstUtilsKt;
032    import org.jetbrains.kotlin.lexer.KtTokens;
033    import org.jetbrains.kotlin.name.Name;
034    import org.jetbrains.kotlin.psi.KtClassOrObject;
035    import org.jetbrains.kotlin.psi.KtEnumEntry;
036    import org.jetbrains.kotlin.psi.KtParameter;
037    import org.jetbrains.kotlin.resolve.DescriptorUtils;
038    import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall;
039    import org.jetbrains.kotlin.types.KotlinType;
040    
041    import java.util.ArrayList;
042    import java.util.Collections;
043    import java.util.List;
044    
045    import static org.jetbrains.kotlin.js.translate.utils.BindingUtils.*;
046    import static org.jetbrains.kotlin.js.translate.utils.FunctionBodyTranslator.setDefaultValueForArguments;
047    import static org.jetbrains.kotlin.js.translate.utils.JsAstUtils.pureFqn;
048    import static org.jetbrains.kotlin.js.translate.utils.PsiUtils.getPrimaryConstructorParameters;
049    import static org.jetbrains.kotlin.resolve.DescriptorUtils.getClassDescriptorForType;
050    
051    public final class ClassInitializerTranslator extends AbstractTranslator {
052        @NotNull
053        private final KtClassOrObject classDeclaration;
054        @NotNull
055        private final List<JsStatement> initializerStatements = new SmartList<JsStatement>();
056        private final JsFunction initFunction;
057        private final TranslationContext context;
058    
059        public ClassInitializerTranslator(
060                @NotNull KtClassOrObject classDeclaration,
061                @NotNull TranslationContext context
062        ) {
063            super(context);
064            this.classDeclaration = classDeclaration;
065            this.initFunction = createInitFunction(classDeclaration, context);
066            this.context = context.contextWithScope(initFunction);
067        }
068    
069        @NotNull
070        @Override
071        protected TranslationContext context() {
072            return context;
073        }
074    
075        @NotNull
076        private static JsFunction createInitFunction(KtClassOrObject declaration, TranslationContext context) {
077            //TODO: it's inconsistent that we have scope for class and function for constructor, currently have problems implementing better way
078            ClassDescriptor classDescriptor = getClassDescriptor(context.bindingContext(), declaration);
079            ConstructorDescriptor primaryConstructor = classDescriptor.getUnsubstitutedPrimaryConstructor();
080    
081            Name name = classDescriptor.getName();
082    
083            JsFunction ctorFunction;
084            if (primaryConstructor != null) {
085                ctorFunction = context.getFunctionObject(primaryConstructor);
086            }
087            else {
088                ctorFunction = new JsFunction(context.scope(), new JsBlock(), "fake constructor for " + name.asString());
089            }
090    
091            // TODO use name from JsName when class annotated by that
092            if (!name.isSpecial()) {
093                ctorFunction.setName(ctorFunction.getScope().declareName(name.asString()));
094            }
095    
096            return ctorFunction;
097        }
098    
099        @NotNull
100        public JsFunction generateInitializeMethod(DelegationTranslator delegationTranslator) {
101            ClassDescriptor classDescriptor = getClassDescriptor(bindingContext(), classDeclaration);
102            addOuterClassReference(classDescriptor);
103            ConstructorDescriptor primaryConstructor = classDescriptor.getUnsubstitutedPrimaryConstructor();
104    
105            if (primaryConstructor != null) {
106                initFunction.getBody().getStatements().addAll(setDefaultValueForArguments(primaryConstructor, context()));
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                mayBeAddCallToSuperMethod(initFunction, classDescriptor);
113            }
114    
115            delegationTranslator.addInitCode(initializerStatements);
116            new InitializerVisitor(initializerStatements).traverseContainer(classDeclaration, context());
117    
118            List<JsStatement> statements = initFunction.getBody().getStatements();
119    
120            for (JsStatement statement : initializerStatements) {
121                if (statement instanceof JsBlock) {
122                    statements.addAll(((JsBlock) statement).getStatements());
123                }
124                else {
125                    statements.add(statement);
126                }
127            }
128    
129            return initFunction;
130        }
131    
132        private void addOuterClassReference(ClassDescriptor classDescriptor) {
133            JsName outerName = context.getOuterClassReference(classDescriptor);
134            if (outerName == null) return;
135    
136            initFunction.getParameters().add(0, new JsParameter(outerName));
137    
138            JsExpression paramRef = pureFqn(outerName, null);
139            JsExpression assignment = JsAstUtils.assignment(pureFqn(outerName, JsLiteral.THIS), paramRef);
140            initFunction.getBody().getStatements().add(new JsExpressionStatement(assignment));
141        }
142    
143        @NotNull
144        public JsExpression generateEnumEntryInstanceCreation(@NotNull KotlinType enumClassType) {
145            ResolvedCall<FunctionDescriptor> superCall = getSuperCall(bindingContext(), classDeclaration);
146    
147            if (superCall == null) {
148                ClassDescriptor classDescriptor = getClassDescriptorForType(enumClassType);
149                JsNameRef reference = context().getQualifiedReference(classDescriptor);
150                return new JsNew(reference);
151            }
152    
153            return CallTranslator.translate(context(), superCall);
154        }
155    
156        private void mayBeAddCallToSuperMethod(JsFunction initializer, @NotNull ClassDescriptor descriptor) {
157            if (classDeclaration.hasModifier(KtTokens.ENUM_KEYWORD)) {
158                addCallToSuperMethod(Collections.<JsExpression>emptyList(), initializer);
159            }
160            else if (hasAncestorClass(bindingContext(), classDeclaration)) {
161                ResolvedCall<FunctionDescriptor> superCall = getSuperCall(bindingContext(), classDeclaration);
162                if (superCall == null) {
163                    if (DescriptorUtils.isEnumEntry(descriptor)) {
164                        addCallToSuperMethod(Collections.<JsExpression>emptyList(), initializer);
165                    }
166                    return;
167                }
168    
169                if (classDeclaration instanceof KtEnumEntry) {
170                    JsExpression expression = CallTranslator.translate(context(), superCall, null);
171                    JsExpression fixedInvocation = AstUtilsKt.toInvocationWith(expression, Collections.<JsExpression>emptyList(), 0,
172                                                                               JsLiteral.THIS);
173                    initializerStatements.add(0, fixedInvocation.makeStmt());
174                }
175                else {
176                    List<JsExpression> arguments = new ArrayList<JsExpression>();
177    
178                    ConstructorDescriptor superDescriptor = (ConstructorDescriptor) superCall.getResultingDescriptor();
179    
180                    List<DeclarationDescriptor> superclassClosure = context.getClassOrConstructorClosure(superDescriptor);
181                    if (superclassClosure != null) {
182                        UsageTracker tracker = context.usageTracker();
183                        assert tracker != null : "Closure exists, therefore UsageTracker must exist too. Translating constructor of " +
184                                                 descriptor;
185                        for (DeclarationDescriptor capturedValue : superclassClosure) {
186                            tracker.used(capturedValue);
187                            arguments.add(tracker.getCapturedDescriptorToJsName().get(capturedValue).makeRef());
188                        }
189                    }
190    
191                    if (superDescriptor.getContainingDeclaration().isInner() && descriptor.isInner()) {
192                        arguments.add(pureFqn(Namer.OUTER_FIELD_NAME, JsLiteral.THIS));
193                    }
194    
195                    if (!DescriptorUtils.isAnonymousObject(descriptor)) {
196                        arguments.addAll(CallArgumentTranslator.translate(superCall, null, context()).getTranslateArguments());
197                    }
198                    else {
199                        for (ValueParameterDescriptor parameter : superDescriptor.getValueParameters()) {
200                            JsName parameterName = context.getNameForDescriptor(parameter);
201                            arguments.add(parameterName.makeRef());
202                            initializer.getParameters().add(new JsParameter(parameterName));
203                        }
204                    }
205    
206                    addCallToSuperMethod(arguments, initializer);
207                }
208            }
209        }
210    
211        private void addCallToSuperMethod(@NotNull List<JsExpression> arguments, JsFunction initializer) {
212            if (initializer.getName() == null) {
213                JsName ref = context().scope().declareName(Namer.CALLEE_NAME);
214                initializer.setName(ref);
215            }
216    
217            JsInvocation call = new JsInvocation(Namer.getFunctionCallRef(Namer.superMethodNameRef(initializer.getName())));
218            call.getArguments().add(JsLiteral.THIS);
219            call.getArguments().addAll(arguments);
220            initializerStatements.add(0, call.makeStmt());
221        }
222    
223        @NotNull
224        private List<JsParameter> translatePrimaryConstructorParameters() {
225            List<KtParameter> parameterList = getPrimaryConstructorParameters(classDeclaration);
226            List<JsParameter> result = new ArrayList<JsParameter>();
227            for (KtParameter jetParameter : parameterList) {
228                result.add(translateParameter(jetParameter));
229            }
230            return result;
231        }
232    
233        @NotNull
234        private JsParameter translateParameter(@NotNull KtParameter jetParameter) {
235            DeclarationDescriptor parameterDescriptor =
236                    getDescriptorForElement(bindingContext(), jetParameter);
237            JsName parameterName = context().getNameForDescriptor(parameterDescriptor);
238            JsParameter jsParameter = new JsParameter(parameterName);
239            mayBeAddInitializerStatementForProperty(jsParameter, jetParameter);
240            return jsParameter;
241        }
242    
243        private void mayBeAddInitializerStatementForProperty(@NotNull JsParameter jsParameter,
244                @NotNull KtParameter jetParameter) {
245            PropertyDescriptor propertyDescriptor =
246                    getPropertyDescriptorForConstructorParameter(bindingContext(), jetParameter);
247            if (propertyDescriptor == null) {
248                return;
249            }
250            JsNameRef initialValueForProperty = jsParameter.getName().makeRef();
251            addInitializerOrPropertyDefinition(initialValueForProperty, propertyDescriptor);
252        }
253    
254        private void addInitializerOrPropertyDefinition(@NotNull JsNameRef initialValue, @NotNull PropertyDescriptor propertyDescriptor) {
255            initializerStatements.add(InitializerUtils.generateInitializerForProperty(context(), propertyDescriptor, initialValue));
256        }
257    }