001 /*
002 * Copyright 2010-2015 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.annotations.Nullable;
023 import org.jetbrains.kotlin.descriptors.*;
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.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.AstUtilsKt;
031 import org.jetbrains.kotlin.lexer.KtTokens;
032 import org.jetbrains.kotlin.psi.*;
033 import org.jetbrains.kotlin.resolve.calls.callUtil.CallUtilKt;
034 import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall;
035 import org.jetbrains.kotlin.types.KotlinType;
036
037 import java.util.ArrayList;
038 import java.util.Collections;
039 import java.util.List;
040
041 import static org.jetbrains.kotlin.js.translate.utils.BindingUtils.*;
042 import static org.jetbrains.kotlin.js.translate.utils.FunctionBodyTranslator.setDefaultValueForArguments;
043 import static org.jetbrains.kotlin.js.translate.utils.PsiUtils.getPrimaryConstructorParameters;
044 import static org.jetbrains.kotlin.resolve.DescriptorUtils.getClassDescriptorForType;
045
046 public final class ClassInitializerTranslator extends AbstractTranslator {
047 @NotNull
048 private final KtClassOrObject classDeclaration;
049 @NotNull
050 private final List<JsStatement> initializerStatements = new SmartList<JsStatement>();
051 private final JsFunction initFunction;
052 private final TranslationContext context;
053
054 public ClassInitializerTranslator(
055 @NotNull KtClassOrObject classDeclaration,
056 @NotNull TranslationContext context
057 ) {
058 super(context);
059 this.classDeclaration = classDeclaration;
060 this.initFunction = createInitFunction(classDeclaration, context);
061 this.context = context.contextWithScope(initFunction);
062 }
063
064 @NotNull
065 @Override
066 protected TranslationContext context() {
067 return context;
068 }
069
070 @NotNull
071 private static JsFunction createInitFunction(KtClassOrObject declaration, TranslationContext context) {
072 //TODO: it's inconsistent that we have scope for class and function for constructor, currently have problems implementing better way
073 ClassDescriptor classDescriptor = getClassDescriptor(context.bindingContext(), declaration);
074 ConstructorDescriptor primaryConstructor = classDescriptor.getUnsubstitutedPrimaryConstructor();
075
076 if (primaryConstructor != null) {
077 return context.getFunctionObject(primaryConstructor);
078 }
079 else {
080 return new JsFunction(context.scope(), new JsBlock(), "fake constructor for " + classDescriptor.getName().asString());
081 }
082 }
083
084 @NotNull
085 public JsFunction generateInitializeMethod(DelegationTranslator delegationTranslator) {
086 ClassDescriptor classDescriptor = getClassDescriptor(bindingContext(), classDeclaration);
087 ConstructorDescriptor primaryConstructor = classDescriptor.getUnsubstitutedPrimaryConstructor();
088
089 if (primaryConstructor != null) {
090 initFunction.getBody().getStatements().addAll(setDefaultValueForArguments(primaryConstructor, context()));
091
092 //NOTE: while we translate constructor parameters we also add property initializer statements
093 // for properties declared as constructor parameters
094 initFunction.getParameters().addAll(translatePrimaryConstructorParameters());
095
096 mayBeAddCallToSuperMethod(initFunction);
097 }
098
099 delegationTranslator.addInitCode(initializerStatements);
100 new InitializerVisitor(initializerStatements).traverseContainer(classDeclaration, context());
101
102 List<JsStatement> statements = initFunction.getBody().getStatements();
103
104 for (JsStatement statement : initializerStatements) {
105 if (statement instanceof JsBlock) {
106 statements.addAll(((JsBlock) statement).getStatements());
107 }
108 else {
109 statements.add(statement);
110 }
111 }
112
113 return initFunction;
114 }
115
116 @NotNull
117 public JsExpression generateEnumEntryInstanceCreation(@NotNull KotlinType enumClassType) {
118 ResolvedCall<FunctionDescriptor> superCall = getSuperCall();
119
120 if (superCall == null) {
121 ClassDescriptor classDescriptor = getClassDescriptorForType(enumClassType);
122 JsNameRef reference = context().getQualifiedReference(classDescriptor);
123 return new JsNew(reference);
124 }
125
126 return CallTranslator.translate(context(), superCall);
127 }
128
129 private void mayBeAddCallToSuperMethod(JsFunction initializer) {
130 if (classDeclaration.hasModifier(KtTokens.ENUM_KEYWORD)) {
131 addCallToSuperMethod(Collections.<JsExpression>emptyList(), initializer);
132 return;
133 }
134 if (hasAncestorClass(bindingContext(), classDeclaration)) {
135 ResolvedCall<FunctionDescriptor> superCall = getSuperCall();
136 if (superCall == null) return;
137
138 if (classDeclaration instanceof KtEnumEntry) {
139 JsExpression expression = CallTranslator.translate(context(), superCall, null);
140 JsExpression fixedInvocation = AstUtilsKt.toInvocationWith(expression, JsLiteral.THIS);
141 initializerStatements.add(0, fixedInvocation.makeStmt());
142 }
143 else {
144 List<JsExpression> arguments = CallArgumentTranslator.translate(superCall, null, context()).getTranslateArguments();
145 addCallToSuperMethod(arguments, initializer);
146 }
147 }
148 }
149
150 private void addCallToSuperMethod(@NotNull List<JsExpression> arguments, JsFunction initializer) {
151 JsName ref = context().scope().declareName(Namer.CALLEE_NAME);
152 initializer.setName(ref);
153 JsInvocation call = new JsInvocation(Namer.getFunctionCallRef(Namer.superMethodNameRef(ref)));
154 call.getArguments().add(JsLiteral.THIS);
155 call.getArguments().addAll(arguments);
156 initializerStatements.add(0, call.makeStmt());
157 }
158
159 @Nullable
160 private ResolvedCall<FunctionDescriptor> getSuperCall() {
161 for (KtSuperTypeListEntry specifier : classDeclaration.getSuperTypeListEntries()) {
162 if (specifier instanceof KtSuperTypeCallEntry) {
163 KtSuperTypeCallEntry superCall = (KtSuperTypeCallEntry) specifier;
164 //noinspection unchecked
165 return (ResolvedCall<FunctionDescriptor>) CallUtilKt.getResolvedCallWithAssert(superCall, bindingContext());
166 }
167 }
168 return null;
169 }
170
171 @NotNull
172 List<JsParameter> translatePrimaryConstructorParameters() {
173 List<KtParameter> parameterList = getPrimaryConstructorParameters(classDeclaration);
174 List<JsParameter> result = new ArrayList<JsParameter>();
175 for (KtParameter jetParameter : parameterList) {
176 result.add(translateParameter(jetParameter));
177 }
178 return result;
179 }
180
181 @NotNull
182 private JsParameter translateParameter(@NotNull KtParameter jetParameter) {
183 DeclarationDescriptor parameterDescriptor =
184 getDescriptorForElement(bindingContext(), jetParameter);
185 JsName parameterName = context().getNameForDescriptor(parameterDescriptor);
186 JsParameter jsParameter = new JsParameter(parameterName);
187 mayBeAddInitializerStatementForProperty(jsParameter, jetParameter);
188 return jsParameter;
189 }
190
191 private void mayBeAddInitializerStatementForProperty(@NotNull JsParameter jsParameter,
192 @NotNull KtParameter jetParameter) {
193 PropertyDescriptor propertyDescriptor =
194 getPropertyDescriptorForConstructorParameter(bindingContext(), jetParameter);
195 if (propertyDescriptor == null) {
196 return;
197 }
198 JsNameRef initialValueForProperty = jsParameter.getName().makeRef();
199 addInitializerOrPropertyDefinition(initialValueForProperty, propertyDescriptor);
200 }
201
202 private void addInitializerOrPropertyDefinition(@NotNull JsNameRef initialValue, @NotNull PropertyDescriptor propertyDescriptor) {
203 initializerStatements.add(InitializerUtils.generateInitializerForProperty(context(), propertyDescriptor, initialValue));
204 }
205 }