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 }