001 /*
002 * Copyright 2010-2013 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.k2js.translate.declaration;
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.jet.backend.common.CodegenUtil;
023 import org.jetbrains.jet.codegen.bridges.Bridge;
024 import org.jetbrains.jet.codegen.bridges.BridgesPackage;
025 import org.jetbrains.jet.lang.descriptors.*;
026 import org.jetbrains.jet.lang.psi.JetClassOrObject;
027 import org.jetbrains.jet.lang.psi.JetObjectDeclaration;
028 import org.jetbrains.jet.lang.psi.JetParameter;
029 import org.jetbrains.jet.lang.types.JetType;
030 import org.jetbrains.jet.lang.types.TypeConstructor;
031 import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns;
032 import org.jetbrains.k2js.translate.context.DefinitionPlace;
033 import org.jetbrains.k2js.translate.context.Namer;
034 import org.jetbrains.k2js.translate.context.TranslationContext;
035 import org.jetbrains.k2js.translate.declaration.propertyTranslator.PropertyTranslatorPackage;
036 import org.jetbrains.k2js.translate.expression.ExpressionPackage;
037 import org.jetbrains.k2js.translate.general.AbstractTranslator;
038 import org.jetbrains.k2js.translate.initializer.ClassInitializerTranslator;
039 import org.jetbrains.k2js.translate.utils.JsAstUtils;
040 import org.jetbrains.k2js.translate.utils.UtilsPackage;
041
042 import java.util.*;
043
044 import static org.jetbrains.jet.lang.resolve.DescriptorUtils.*;
045 import static org.jetbrains.jet.lang.types.TypeUtils.topologicallySortSuperclassesAndRecordAllInstances;
046 import static org.jetbrains.k2js.translate.reference.ReferenceTranslator.translateAsFQReference;
047 import static org.jetbrains.k2js.translate.utils.BindingUtils.getClassDescriptor;
048 import static org.jetbrains.k2js.translate.utils.BindingUtils.getPropertyDescriptorForConstructorParameter;
049 import static org.jetbrains.k2js.translate.utils.JsDescriptorUtils.*;
050 import static org.jetbrains.k2js.translate.utils.PsiUtils.getPrimaryConstructorParameters;
051 import static org.jetbrains.k2js.translate.utils.TranslationUtils.simpleReturnFunction;
052 import static org.jetbrains.k2js.translate.utils.UtilsPackage.*;
053
054 /**
055 * Generates a definition of a single class.
056 */
057 public final class ClassTranslator extends AbstractTranslator {
058 @NotNull
059 private final JetClassOrObject classDeclaration;
060
061 @NotNull
062 private final ClassDescriptor descriptor;
063
064 @NotNull
065 public static JsInvocation generateClassCreation(@NotNull JetClassOrObject classDeclaration, @NotNull TranslationContext context) {
066 return new ClassTranslator(classDeclaration, context).translate();
067 }
068
069 @NotNull
070 public static JsExpression generateObjectLiteral(@NotNull JetObjectDeclaration objectDeclaration, @NotNull TranslationContext context) {
071 return new ClassTranslator(objectDeclaration, context).translateObjectLiteralExpression();
072 }
073
074 private ClassTranslator(@NotNull JetClassOrObject classDeclaration, @NotNull TranslationContext context) {
075 super(context);
076 this.classDeclaration = classDeclaration;
077 this.descriptor = getClassDescriptor(context.bindingContext(), classDeclaration);
078 }
079
080 @NotNull
081 private JsExpression translateObjectLiteralExpression() {
082 ClassDescriptor containingClass = getContainingClass(descriptor);
083 if (containingClass == null) {
084 return translate(context());
085 }
086
087 return translateObjectInsideClass(context());
088 }
089
090 @NotNull
091 public JsInvocation translate() {
092 return translate(context());
093 }
094
095 @NotNull
096 public JsInvocation translate(@NotNull TranslationContext declarationContext) {
097 return new JsInvocation(context().namer().classCreateInvocation(descriptor), getClassCreateInvocationArguments(declarationContext));
098 }
099
100 private boolean isTrait() {
101 return descriptor.getKind().equals(ClassKind.TRAIT);
102 }
103
104 @NotNull
105 private List<JsExpression> getClassCreateInvocationArguments(@NotNull TranslationContext declarationContext) {
106 List<JsExpression> invocationArguments = new ArrayList<JsExpression>();
107
108 List<JsPropertyInitializer> properties = new SmartList<JsPropertyInitializer>();
109 List<JsPropertyInitializer> staticProperties = new SmartList<JsPropertyInitializer>();
110
111 boolean isTopLevelDeclaration = context() == declarationContext;
112
113 JsNameRef qualifiedReference = null;
114 if (isTopLevelDeclaration) {
115 DefinitionPlace definitionPlace = null;
116
117 if (!descriptor.getKind().isSingleton() && !isAnonymousObject(descriptor)) {
118 qualifiedReference = declarationContext.getQualifiedReference(descriptor);
119 JsScope scope = context().getScopeForDescriptor(descriptor);
120 definitionPlace = new DefinitionPlace((JsObjectScope) scope, qualifiedReference, staticProperties);
121 }
122
123 declarationContext = declarationContext.newDeclaration(descriptor, definitionPlace);
124 }
125
126 declarationContext = fixContextForClassObjectAccessing(declarationContext);
127
128 invocationArguments.add(getSuperclassReferences(declarationContext));
129 DelegationTranslator delegationTranslator = new DelegationTranslator(classDeclaration, context());
130 if (!isTrait()) {
131 JsFunction initializer = new ClassInitializerTranslator(classDeclaration, declarationContext).generateInitializeMethod(delegationTranslator);
132 invocationArguments.add(initializer.getBody().getStatements().isEmpty() ? JsLiteral.NULL : initializer);
133 }
134
135 translatePropertiesAsConstructorParameters(declarationContext, properties);
136 DeclarationBodyVisitor bodyVisitor = new DeclarationBodyVisitor(properties, staticProperties);
137 bodyVisitor.traverseContainer(classDeclaration, declarationContext);
138 delegationTranslator.generateDelegated(properties);
139
140 if (KotlinBuiltIns.getInstance().isData(descriptor)) {
141 new JsDataClassGenerator(classDeclaration, declarationContext, properties).generate();
142 }
143
144 if (isEnumClass(descriptor)) {
145 JsObjectLiteral enumEntries = new JsObjectLiteral(bodyVisitor.getEnumEntryList(), true);
146 JsFunction function = simpleReturnFunction(declarationContext.getScopeForDescriptor(descriptor), enumEntries);
147 invocationArguments.add(function);
148 }
149
150 generatedBridgeMethods(properties);
151
152 boolean hasStaticProperties = !staticProperties.isEmpty();
153 if (!properties.isEmpty() || hasStaticProperties) {
154 if (properties.isEmpty()) {
155 invocationArguments.add(JsLiteral.NULL);
156 }
157 else {
158 if (qualifiedReference != null) {
159 // about "prototype" - see http://code.google.com/p/jsdoc-toolkit/wiki/TagLends
160 invocationArguments.add(new JsDocComment(JsAstUtils.LENDS_JS_DOC_TAG, new JsNameRef("prototype", qualifiedReference)));
161 }
162 invocationArguments.add(new JsObjectLiteral(properties, true));
163 }
164 }
165 if (hasStaticProperties) {
166 invocationArguments.add(new JsDocComment(JsAstUtils.LENDS_JS_DOC_TAG, qualifiedReference));
167 invocationArguments.add(new JsObjectLiteral(staticProperties, true));
168 }
169
170 return invocationArguments;
171 }
172
173 private TranslationContext fixContextForClassObjectAccessing(TranslationContext declarationContext) {
174 // In Kotlin we can access to class object members without qualifier just by name, but we should translate it to access with FQ name.
175 // So create alias for class object receiver parameter.
176 ClassDescriptor classObjectDescriptor = descriptor.getClassObjectDescriptor();
177 if (classObjectDescriptor != null) {
178 JsExpression referenceToClass = translateAsFQReference(classObjectDescriptor.getContainingDeclaration(), declarationContext);
179 JsExpression classObjectAccessor = Namer.getClassObjectAccessor(referenceToClass);
180 ReceiverParameterDescriptor classObjectReceiver = getReceiverParameterForDeclaration(classObjectDescriptor);
181 declarationContext.aliasingContext().registerAlias(classObjectReceiver, classObjectAccessor);
182 }
183
184 // Overlap alias of class object receiver for accessing from containing class(see previous if block),
185 // because inside class object we should use simple name for access.
186 if (descriptor.getKind() == ClassKind.CLASS_OBJECT) {
187 declarationContext = declarationContext.innerContextWithAliased(descriptor.getThisAsReceiverParameter(), JsLiteral.THIS);
188 }
189
190 return declarationContext;
191 }
192
193 private JsExpression getSuperclassReferences(@NotNull TranslationContext declarationContext) {
194 List<JsExpression> superClassReferences = getSupertypesNameReferences();
195 if (superClassReferences.isEmpty()) {
196 return JsLiteral.NULL;
197 } else {
198 return simpleReturnFunction(declarationContext.scope(), new JsArrayLiteral(superClassReferences));
199 }
200 }
201
202 @NotNull
203 private List<JsExpression> getSupertypesNameReferences() {
204 List<JetType> supertypes = getSupertypesWithoutFakes(descriptor);
205 if (supertypes.isEmpty()) {
206 return Collections.emptyList();
207 }
208 if (supertypes.size() == 1) {
209 JetType type = supertypes.get(0);
210 ClassDescriptor supertypeDescriptor = getClassDescriptorForType(type);
211 return Collections.<JsExpression>singletonList(getClassReference(supertypeDescriptor));
212 }
213
214 Set<TypeConstructor> supertypeConstructors = new HashSet<TypeConstructor>();
215 for (JetType type : supertypes) {
216 supertypeConstructors.add(type.getConstructor());
217 }
218 List<TypeConstructor> sortedAllSuperTypes = topologicallySortSuperclassesAndRecordAllInstances(descriptor.getDefaultType(),
219 new HashMap<TypeConstructor, Set<JetType>>(),
220 new HashSet<TypeConstructor>());
221 List<JsExpression> supertypesRefs = new ArrayList<JsExpression>();
222 for (TypeConstructor typeConstructor : sortedAllSuperTypes) {
223 if (supertypeConstructors.contains(typeConstructor)) {
224 ClassDescriptor supertypeDescriptor = getClassDescriptorForTypeConstructor(typeConstructor);
225 supertypesRefs.add(getClassReference(supertypeDescriptor));
226 }
227 }
228 return supertypesRefs;
229 }
230
231 @NotNull
232 private JsNameRef getClassReference(@NotNull ClassDescriptor superClassDescriptor) {
233 return context().getQualifiedReference(superClassDescriptor);
234 }
235
236 private void translatePropertiesAsConstructorParameters(@NotNull TranslationContext classDeclarationContext,
237 @NotNull List<JsPropertyInitializer> result) {
238 for (JetParameter parameter : getPrimaryConstructorParameters(classDeclaration)) {
239 PropertyDescriptor descriptor = getPropertyDescriptorForConstructorParameter(bindingContext(), parameter);
240 if (descriptor != null) {
241 PropertyTranslatorPackage.translateAccessors(descriptor, result, classDeclarationContext);
242 }
243 }
244 }
245
246 @NotNull
247 private JsExpression translateObjectInsideClass(@NotNull TranslationContext outerClassContext) {
248 JsFunction fun = new JsFunction(outerClassContext.scope(), new JsBlock(), "initializer for " + descriptor.getName().asString());
249 TranslationContext funContext = outerClassContext.newFunctionBodyWithUsageTracker(fun, descriptor);
250
251 fun.getBody().getStatements().add(new JsReturn(translate(funContext)));
252
253 return ExpressionPackage.withCapturedParameters(fun, funContext, outerClassContext, descriptor);
254 }
255
256 private void generatedBridgeMethods(@NotNull List<JsPropertyInitializer> properties) {
257 if (isTrait()) return;
258
259 generateBridgesToTraitImpl(properties);
260
261 generateOtherBridges(properties);
262 }
263
264 private void generateBridgesToTraitImpl(List<JsPropertyInitializer> properties) {
265 for(Map.Entry<FunctionDescriptor, FunctionDescriptor> entry : CodegenUtil.getTraitMethods(descriptor).entrySet()) {
266 if (!areNamesEqual(entry.getKey(), entry.getValue())) {
267 properties.add(generateDelegateCall(entry.getValue(), entry.getKey(), JsLiteral.THIS, context()));
268 }
269 }
270 }
271
272 private void generateOtherBridges(List<JsPropertyInitializer> properties) {
273 for (DeclarationDescriptor memberDescriptor : descriptor.getDefaultType().getMemberScope().getAllDescriptors()) {
274 if (memberDescriptor instanceof FunctionDescriptor) {
275 FunctionDescriptor functionDescriptor = (FunctionDescriptor) memberDescriptor;
276 Set<Bridge<FunctionDescriptor>> bridgesToGenerate =
277 BridgesPackage.generateBridgesForFunctionDescriptor(functionDescriptor, UtilsPackage.<FunctionDescriptor>getID());
278
279 for (Bridge<FunctionDescriptor> bridge : bridgesToGenerate) {
280 generateBridge(bridge, properties);
281 }
282 }
283 }
284 }
285
286 private void generateBridge(
287 @NotNull Bridge<FunctionDescriptor> bridge,
288 @NotNull List<JsPropertyInitializer> properties
289 ) {
290 FunctionDescriptor fromDescriptor = bridge.getFrom();
291 FunctionDescriptor toDescriptor = bridge.getTo();
292 if (areNamesEqual(fromDescriptor, toDescriptor)) return;
293
294 if (fromDescriptor.getKind().isReal() &&
295 fromDescriptor.getModality() != Modality.ABSTRACT &&
296 !toDescriptor.getKind().isReal()) return;
297
298 properties.add(generateDelegateCall(fromDescriptor, toDescriptor, JsLiteral.THIS, context()));
299 }
300
301 private boolean areNamesEqual(@NotNull FunctionDescriptor first, @NotNull FunctionDescriptor second) {
302 JsName firstName = context().getNameForDescriptor(first);
303 JsName secondName = context().getNameForDescriptor(second);
304 return firstName.getIdent().equals(secondName.getIdent());
305 }
306 }