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 }